sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00

Synced files:
- Quote wizard frontend (all components, hooks, types, config)
- API updates (config, models, routers, schemas, services)
- Client work (bg-builders, gurushow)
- Scripts (BGB Lesley termination, CIPP, Datto, migration)
- Temp files (Bardach contacts, VWP investigation, misc)
- Credentials and session logs
- Email service, PHP API, session logs

Machine: ACG-M-L5090
Timestamp: 2026-03-10 19:11:00

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 19:59:08 -07:00
parent a1a19f8c00
commit fa15b03180
169 changed files with 879909 additions and 1243 deletions

View File

@@ -0,0 +1,37 @@
# BG Builders - Assign Exchange Administrator role to Claude-MSP-Access service principal
# Required for Exchange Online app-only auth (Set-Mailbox, litigation hold, etc.)
# Run from interactive PowerShell as sysadmin@bgbuildersllc.com
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
$spId = "9c04bb74-c2d0-4d83-ab54-9c43a9daaa23" # Claude-MSP-Access SP in BG Builders
$exoRoleId = "87706939-e519-4028-a73e-a6a7f04b4a20" # Exchange Administrator
Write-Output "Connecting to Graph..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Identity.DirectoryManagement
Connect-MgGraph -TenantId $tenantId -Scopes 'RoleManagement.ReadWrite.Directory' -NoWelcome
Write-Output "[OK] Connected"
Write-Output "Assigning Exchange Administrator to Claude-MSP-Access..."
$body = @{
"@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$spId"
}
New-MgDirectoryRoleMemberByRef -DirectoryRoleId $exoRoleId -BodyParameter $body
Write-Output "[OK] Exchange Administrator role assigned"
# Now set litigation hold on Lesley
Write-Output "`nConnecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected"
Write-Output "Setting litigation hold on Lesley's mailbox..."
Set-Mailbox -Identity "lesley@bgbuildersllc.com" -LitigationHoldEnabled $true -LitigationHoldDuration Unlimited
Write-Output "[OK] Litigation hold enabled"
Write-Output "`nVerifying..."
Get-Mailbox -Identity "lesley@bgbuildersllc.com" | Format-List DisplayName,LitigationHoldEnabled,LitigationHoldDuration
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph
Write-Output "[OK] Done"

View File

@@ -0,0 +1,81 @@
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
Import-Module Microsoft.Graph.Groups
Import-Module Microsoft.Graph.Sites
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
$lesleyUPN = "lesley@bgbuildersllc.com"
Write-Output "========================================="
Write-Output " BG Builders - Lesley Roth Ownership Audit"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
Connect-MgGraph -TenantId $tenantId -Scopes 'User.Read.All','Group.Read.All','Sites.Read.All','TeamSettings.Read.All' -NoWelcome
$lesley = Get-MgUser -UserId $lesleyUPN -Property Id,DisplayName
Write-Output "[OK] Lesley ID: $($lesley.Id)"
# --- Check Teams/M365 Group ownership ---
Write-Output "`n--- Teams / M365 Group Ownership ---"
$ownedGroups = Get-MgUserOwnedObject -UserId $lesley.Id -All
if ($ownedGroups) {
foreach ($obj in $ownedGroups) {
$group = Get-MgGroup -GroupId $obj.Id -Property DisplayName,GroupTypes,Mail -ErrorAction SilentlyContinue
if ($group) {
$isTeam = $group.GroupTypes -contains "Unified"
$type = if ($isTeam) { "M365 Group/Team" } else { "Group" }
Write-Output " [OWNER] $type : $($group.DisplayName) ($($group.Mail))"
# Check if sole owner
$owners = Get-MgGroupOwner -GroupId $obj.Id -All
if ($owners.Count -le 1) {
Write-Output " [WARNING] SOLE OWNER - needs transfer before termination"
} else {
Write-Output " [OK] Has $($owners.Count) owners total"
}
}
}
} else {
Write-Output " [INFO] Lesley does not own any groups or teams"
}
# --- Check group memberships ---
Write-Output "`n--- Group / Team Memberships ---"
$memberships = Get-MgUserMemberOf -UserId $lesley.Id -All
foreach ($mem in $memberships) {
$group = Get-MgGroup -GroupId $mem.Id -Property DisplayName,GroupTypes,Mail -ErrorAction SilentlyContinue
if ($group) {
$isTeam = $group.GroupTypes -contains "Unified"
$type = if ($isTeam) { "M365 Group/Team" } else { "Security/DL Group" }
Write-Output " [MEMBER] $type : $($group.DisplayName) ($($group.Mail))"
}
}
# --- Check SharePoint site ownership ---
Write-Output "`n--- SharePoint Sites ---"
try {
$sites = Get-MgSite -Search "*" -All -Property DisplayName,WebUrl 2>$null
if ($sites) {
foreach ($site in $sites) {
try {
$sitePermissions = Get-MgSitePermission -SiteId $site.Id -ErrorAction SilentlyContinue 2>$null
} catch {
# Fall through - permissions API may not be available on all sites
}
Write-Output " [SITE] $($site.DisplayName) - $($site.WebUrl)"
}
}
} catch {
Write-Output " [INFO] Could not enumerate SharePoint sites (may need SharePoint admin role)"
}
# --- Check distribution group membership via Exchange ---
Write-Output "`n--- Distribution List Memberships (requires Exchange connection) ---"
Write-Output " [INFO] Run separately via Exchange Online to check DL memberships"
Write-Output "`n========================================="
Write-Output " Audit Complete"
Write-Output "========================================="
Disconnect-MgGraph

View File

@@ -0,0 +1,11 @@
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
Connect-MgGraph -TenantId 'ededa4fb-f6eb-4398-851d-5eb3e11fab27' -Scopes 'User.Read.All' -NoWelcome
# List all users to find Leslie
$allUsers = Get-MgUser -All -Property DisplayName,Mail,UserPrincipalName,AccountEnabled,Id
Write-Output "--- All Users in Tenant ---"
$allUsers | Format-Table DisplayName,Mail,UserPrincipalName,AccountEnabled -AutoSize
Disconnect-MgGraph

View File

@@ -0,0 +1,102 @@
# BG Builders - Disable Lesley Roth + Wipe Email from Device
# Employee: Lesley Roth (lesley@bgbuildersllc.com)
# Date: 2026-03-09
# Actions:
# 1. Block sign-in
# 2. Revoke all sessions
# 3. Reset password
# 4. Wipe email data from mobile devices (selective wipe + EAS wipe)
$ErrorActionPreference = "Stop"
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
$lesleyUPN = "lesley@bgbuildersllc.com"
Write-Output "========================================="
Write-Output " BG Builders - Disable Lesley Roth"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- STEP 1: Connect to Microsoft Graph ---
Write-Output "`n[STEP 1] Connecting to Microsoft Graph..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
Import-Module Microsoft.Graph.Users.Actions
Connect-MgGraph -TenantId $tenantId -Scopes 'User.ReadWrite.All','Directory.ReadWrite.All','DeviceManagementManagedDevices.ReadWrite.All','DeviceManagementManagedDevices.PrivilegedOperations.All' -NoWelcome
Write-Output "[OK] Connected to Graph"
$lesley = Get-MgUser -UserId $lesleyUPN -Property Id,DisplayName,AccountEnabled,AssignedLicenses
Write-Output "[INFO] Current state: AccountEnabled=$($lesley.AccountEnabled)"
# --- STEP 2: Block sign-in ---
Write-Output "`n[STEP 2] Blocking sign-in..."
Update-MgUser -UserId $lesley.Id -AccountEnabled:$false
Write-Output "[OK] Sign-in blocked"
# --- STEP 3: Revoke all sessions ---
Write-Output "`n[STEP 3] Revoking all active sessions..."
Revoke-MgUserSignInSession -UserId $lesley.Id
Write-Output "[OK] All sessions revoked"
# --- STEP 4: Reset password ---
Write-Output "`n[STEP 4] Resetting password..."
$newPassword = -join ((65..90) + (97..122) + (48..57) + (33,35,36,37,38) | Get-Random -Count 24 | ForEach-Object {[char]$_})
$params = @{
passwordProfile = @{
forceChangePasswordNextSignIn = $true
password = $newPassword
}
}
Update-MgUser -UserId $lesley.Id -BodyParameter $params
Write-Output "[OK] Password reset to random value"
# --- STEP 5: Wipe email from devices (Intune managed) ---
Write-Output "`n[STEP 5] Checking for Intune-managed devices..."
Import-Module Microsoft.Graph.DeviceManagement
$devices = Get-MgDeviceManagementManagedDevice -Filter "userPrincipalName eq '$lesleyUPN'" 2>$null
if ($devices) {
foreach ($device in $devices) {
Write-Output " Found: $($device.DeviceName) ($($device.OperatingSystem)) - ID: $($device.Id)"
Write-Output " Initiating selective wipe (company data only)..."
Invoke-MgRetireDeviceManagementManagedDevice -ManagedDeviceId $device.Id
Write-Output " [OK] Selective wipe queued for $($device.DeviceName)"
}
} else {
Write-Output "[INFO] No Intune-managed devices found"
}
# --- STEP 6: Wipe email from devices (Exchange ActiveSync) ---
Write-Output "`n[STEP 6] Connecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected to Exchange Online"
Write-Output "Checking for ActiveSync devices..."
$easDevices = Get-MobileDevice -Mailbox $lesleyUPN 2>$null
if ($easDevices) {
foreach ($eas in $easDevices) {
Write-Output " Found EAS device: $($eas.FriendlyName) ($($eas.DeviceOS))"
Clear-MobileDevice -Identity $eas.Identity -AccountOnly -Confirm:$false
Write-Output " [OK] Account-only wipe initiated for $($eas.FriendlyName)"
}
Write-Output "[OK] All EAS devices queued for account wipe"
} else {
Write-Output "[INFO] No EAS mobile devices found"
}
# --- DONE ---
Write-Output "`n========================================="
Write-Output " DISABLE + DEVICE WIPE COMPLETE"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
Write-Output ""
Write-Output "Summary:"
Write-Output " [OK] Sign-in blocked"
Write-Output " [OK] Sessions revoked"
Write-Output " [OK] Password reset"
Write-Output " [OK] Device email wipe initiated (Intune + EAS)"
Write-Output ""
Write-Output "[INFO] Mailbox is still accessible - run full termination script"
Write-Output " when ready to convert to shared, remove license, etc."
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph

View File

@@ -0,0 +1,33 @@
# BG Builders - Lesley Exchange steps (run from interactive PowerShell)
# Adds Shelly as delegate + enables litigation hold
$lesleyUPN = "lesley@bgbuildersllc.com"
$shellyUPN = "Shelly@bgbuildersllc.com"
Write-Output "Connecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected"
# Add Shelly as delegate
Write-Output "`nAdding Shelly as delegate..."
Add-MailboxPermission -Identity $lesleyUPN -User $shellyUPN -AccessRights FullAccess -AutoMapping $true
Write-Output "[OK] Shelly granted FullAccess"
Add-RecipientPermission -Identity $lesleyUPN -Trustee $shellyUPN -AccessRights SendAs -Confirm:$false
Write-Output "[OK] Shelly granted SendAs"
# Enable litigation hold
Write-Output "`nEnabling litigation hold..."
Set-Mailbox -Identity $lesleyUPN -LitigationHoldEnabled $true -LitigationHoldDuration Unlimited
Write-Output "[OK] Litigation hold enabled"
# Verify
Write-Output "`nVerifying permissions..."
Get-MailboxPermission -Identity $lesleyUPN | Where-Object { $_.User -notlike "NT AUTHORITY*" -and $_.User -notlike "S-1-*" } | Format-Table User,AccessRights -AutoSize
Write-Output "`nVerifying litigation hold..."
Get-Mailbox -Identity $lesleyUPN | Format-List LitigationHoldEnabled,LitigationHoldDuration
Disconnect-ExchangeOnline -Confirm:$false
Write-Output "[OK] Done"

View File

@@ -0,0 +1,83 @@
# BG Builders - Check and fix inbox rules on lesley shared mailbox
# Run from interactive PowerShell
$lesleyUPN = "lesley@bgbuildersllc.com"
Write-Output "Connecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected"
# Check inbox rules
Write-Output "`n=== INBOX RULES ==="
$rules = Get-InboxRule -Mailbox $lesleyUPN -IncludeHidden
if ($rules) {
foreach ($rule in $rules) {
Write-Output " Rule: $($rule.Name) | Enabled: $($rule.Enabled) | Priority: $($rule.Priority)"
Write-Output " Description: $($rule.Description)"
Write-Output " MoveToFolder: $($rule.MoveToFolder)"
Write-Output " DeleteMessage: $($rule.DeleteMessage)"
Write-Output " SoftDeleteMessage: $($rule.SoftDeleteMessage)"
Write-Output ""
}
# Disable any rules that delete messages
foreach ($rule in $rules) {
if ($rule.DeleteMessage -or $rule.SoftDeleteMessage -or $rule.MoveToFolder -match "Deleted") {
Write-Output "[ALERT] Removing problematic rule: $($rule.Name)"
Remove-InboxRule -Mailbox $lesleyUPN -Identity $rule.Identity -Confirm:$false
Write-Output "[OK] Removed"
}
}
} else {
Write-Output " [OK] No inbox rules found"
}
# Check sweep rules
Write-Output "`n=== SWEEP RULES ==="
try {
$sweep = Get-SweepRule -Mailbox $lesleyUPN
if ($sweep) {
foreach ($s in $sweep) {
Write-Output " Rule: $($s.Name) | Enabled: $($s.Enabled)"
Write-Output " SourceFolder: $($s.SourceFolder)"
Write-Output " DestFolder: $($s.DestFolder)"
Write-Output " KeepLatest: $($s.KeepLatest)"
Write-Output ""
}
# Remove sweep rules
foreach ($s in $sweep) {
Write-Output "[ALERT] Removing sweep rule: $($s.Name)"
Remove-SweepRule -Identity $s.Identity -Mailbox $lesleyUPN -Confirm:$false
Write-Output "[OK] Removed"
}
} else {
Write-Output " [OK] No sweep rules found"
}
} catch {
Write-Output " [INFO] Sweep rules not available: $_"
}
# Check mailbox type and forwarding
Write-Output "`n=== MAILBOX STATUS ==="
$mb = Get-Mailbox -Identity $lesleyUPN
Write-Output " Type: $($mb.RecipientTypeDetails)"
Write-Output " Forwarding: $($mb.ForwardingAddress)"
Write-Output " ForwardingSMTP: $($mb.ForwardingSmtpAddress)"
Write-Output " DeliverToMailboxAndForward: $($mb.DeliverToMailboxAndForward)"
Write-Output " HiddenFromGAL: $($mb.HiddenFromAddressListsEnabled)"
Write-Output " LitigationHold: $($mb.LitigationHoldEnabled)"
# Check transport rules affecting this mailbox
Write-Output "`n=== TRANSPORT RULES ==="
$transport = Get-TransportRule | Where-Object { $_.State -eq "Enabled" }
if ($transport) {
foreach ($t in $transport) {
Write-Output " Rule: $($t.Name) | Priority: $($t.Priority)"
}
} else {
Write-Output " [OK] No transport rules"
}
Disconnect-ExchangeOnline -Confirm:$false
Write-Output "`n[OK] Done"

View File

@@ -0,0 +1,62 @@
=========================================
LESLEY ROTH - 72-HOUR MAIL ACTIVITY REPORT
Generated: 2026-03-09 09:30:46
Window: 2026-03-06 09:30 to 2026-03-09 09:30
=========================================
=========================================
SENT MESSAGES (0 total)
=========================================
[NONE] No sent messages in the last 72 hours
=========================================
RECEIVED MESSAGES (5 total)
=========================================
Date: 2026-03-09 09:53:49
From: Gallagher.NoReply@Vertafore.com
Subject: Coyote Landing - 23-09001.Coyote - Enrollment Status Report From AJG - 03/09/2026 - By Contractor Name (All Tier)
Status: Delivered
---
Date: 2026-03-09 09:22:52
From: Gallagher.NoReply@Vertafore.com
Subject: Coyote Landing - 23-09001.Coyote - Enrollment Status Report From AJG - 03/09/2026 - By Contractor Name (First Tier)
Status: Delivered
---
Date: 2026-03-09 08:32:29
From: Gallagher.NoReply@Vertafore.com
Subject: Coyote Landing / EmpirePaving-BGBuild-23-09001.Coyote / Missing/Incomplete Insurance Cost Worksheet
Status: Delivered
---
Date: 2026-03-09 08:17:05
From: Gallagher.NoReply@Vertafore.com
Subject: Coyote Landing / EmpirePaving-BGBuild-23-09001.Coyote / Enrollment Incomplete
Status: Delivered
---
Date: 2026-03-06 22:09:29
From: notifications@s.usa.experian.com
Subject: Lesley, your Experian account info recently changed.
Status: Delivered
---
=========================================
DELETED ITEMS (0 total)
=========================================
[NONE] No deleted items in the last 72 hours
=========================================
INBOX RULES
=========================================
[NONE] No inbox rules configured
=========================================
FORWARDING CONFIGURATION
=========================================
ForwardingAddress:
ForwardingSmtpAddress:
DeliverToMailboxAndForward: False
[OK] No forwarding configured

View File

@@ -0,0 +1,150 @@
# BG Builders - Lesley Roth 72-Hour Mail Activity Report
# Pulls sent mail (message trace) and deleted items (mailbox audit log)
# Date: 2026-03-09
$ErrorActionPreference = "Stop"
$lesleyUPN = "lesley@bgbuildersllc.com"
$startDate = (Get-Date).AddHours(-72)
$endDate = Get-Date
$reportPath = "D:\ClaudeTools\scripts\bgb-lesley-mail-report-$(Get-Date -Format 'yyyyMMdd').txt"
Write-Output "========================================="
Write-Output " BG Builders - Lesley Roth Mail Report"
Write-Output " 72-Hour Window: $($startDate.ToString('yyyy-MM-dd HH:mm')) to $($endDate.ToString('yyyy-MM-dd HH:mm'))"
Write-Output "========================================="
# --- Connect to Exchange Online ---
Write-Output "`n[STEP 1] Connecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected"
# Start building report
$report = @()
$report += "========================================="
$report += " LESLEY ROTH - 72-HOUR MAIL ACTIVITY REPORT"
$report += " Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$report += " Window: $($startDate.ToString('yyyy-MM-dd HH:mm')) to $($endDate.ToString('yyyy-MM-dd HH:mm'))"
$report += "========================================="
# --- SENT MAIL (Message Trace) ---
Write-Output "`n[STEP 2] Pulling sent mail via message trace..."
$sentMessages = Get-MessageTraceV2 -SenderAddress $lesleyUPN -StartDate $startDate -EndDate $endDate
$report += ""
$report += "=========================================`n SENT MESSAGES ($($sentMessages.Count) total)`n========================================="
if ($sentMessages.Count -gt 0) {
$sentMessages | Sort-Object Received -Descending | ForEach-Object {
$report += ""
$report += " Date: $($_.Received.ToString('yyyy-MM-dd HH:mm:ss'))"
$report += " To: $($_.RecipientAddress)"
$report += " Subject: $($_.Subject)"
$report += " Status: $($_.Status)"
$report += " Size: $([math]::Round($_.Size / 1KB, 1)) KB"
$report += " MsgID: $($_.MessageId)"
$report += " ---"
}
} else {
$report += " [NONE] No sent messages in the last 72 hours"
}
Write-Output "[OK] Found $($sentMessages.Count) sent messages"
# --- RECEIVED MAIL (Message Trace) ---
Write-Output "`n[STEP 3] Pulling received mail via message trace..."
$receivedMessages = Get-MessageTraceV2 -RecipientAddress $lesleyUPN -StartDate $startDate -EndDate $endDate
$report += ""
$report += "=========================================`n RECEIVED MESSAGES ($($receivedMessages.Count) total)`n========================================="
if ($receivedMessages.Count -gt 0) {
$receivedMessages | Sort-Object Received -Descending | ForEach-Object {
$report += ""
$report += " Date: $($_.Received.ToString('yyyy-MM-dd HH:mm:ss'))"
$report += " From: $($_.SenderAddress)"
$report += " Subject: $($_.Subject)"
$report += " Status: $($_.Status)"
$report += " ---"
}
} else {
$report += " [NONE] No received messages in the last 72 hours"
}
Write-Output "[OK] Found $($receivedMessages.Count) received messages"
# --- DELETED ITEMS (Mailbox Audit Log) ---
Write-Output "`n[STEP 4] Pulling deleted items via mailbox audit log..."
# Use Search-UnifiedAuditLog (Search-MailboxAuditLog deprecated Jan 2026)
$deleteOps = "SoftDelete","HardDelete","MoveToDeletedItems"
$deletedItems = Search-UnifiedAuditLog -UserIds $lesleyUPN -Operations ($deleteOps -join ",") -StartDate $startDate -EndDate $endDate -ResultSize 5000
$report += ""
$report += "=========================================`n DELETED ITEMS ($($deletedItems.Count) total)`n========================================="
if ($deletedItems.Count -gt 0) {
$deletedItems | Sort-Object CreationDate -Descending | ForEach-Object {
$auditData = $_.AuditData | ConvertFrom-Json
$report += ""
$report += " Date: $($_.CreationDate)"
$report += " Operation: $($_.Operations)"
$report += " User: $($_.UserIds)"
$report += " Subject: $($auditData.AffectedItems.Subject -join '; ')"
$report += " Folder: $($auditData.Folder.Path)"
$report += " Client: $($auditData.ClientInfoString)"
$report += " ---"
}
} else {
$report += " [NONE] No deleted items in the last 72 hours"
}
Write-Output "[OK] Found $($deletedItems.Count) deleted items"
# --- INBOX RULES (check for forwarding/auto-delete) ---
Write-Output "`n[STEP 5] Checking inbox rules..."
$rules = Get-InboxRule -Mailbox $lesleyUPN 2>$null
$report += ""
$report += "=========================================`n INBOX RULES`n========================================="
if ($rules) {
foreach ($rule in $rules) {
$report += ""
$report += " Name: $($rule.Name)"
$report += " Enabled: $($rule.Enabled)"
$report += " Priority: $($rule.Priority)"
if ($rule.ForwardTo) { $report += " ForwardTo: $($rule.ForwardTo -join '; ')" }
if ($rule.RedirectTo) { $report += " RedirectTo: $($rule.RedirectTo -join '; ')" }
if ($rule.DeleteMessage) { $report += " [WARNING] Auto-delete enabled" }
$report += " ---"
}
} else {
$report += " [NONE] No inbox rules configured"
}
Write-Output "[OK] Rules checked"
# --- FORWARDING CONFIG ---
Write-Output "`n[STEP 6] Checking forwarding configuration..."
$mbx = Get-Mailbox -Identity $lesleyUPN | Select-Object ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward
$report += ""
$report += "=========================================`n FORWARDING CONFIGURATION`n========================================="
$report += " ForwardingAddress: $($mbx.ForwardingAddress)"
$report += " ForwardingSmtpAddress: $($mbx.ForwardingSmtpAddress)"
$report += " DeliverToMailboxAndForward: $($mbx.DeliverToMailboxAndForward)"
if ($mbx.ForwardingAddress -or $mbx.ForwardingSmtpAddress) {
$report += " [WARNING] Active forwarding detected!"
} else {
$report += " [OK] No forwarding configured"
}
# --- Write report to file ---
$report | Out-File -FilePath $reportPath -Encoding UTF8
Write-Output "`n========================================="
Write-Output " REPORT SAVED"
Write-Output " $reportPath"
Write-Output "========================================="
# Also output to console
Write-Output "`n--- REPORT CONTENTS ---"
$report | ForEach-Object { Write-Output $_ }
Disconnect-ExchangeOnline -Confirm:$false
Write-Output "`n[OK] Done"

View File

@@ -0,0 +1,193 @@
#Requires -Modules ExchangeOnlineManagement
<#
.SYNOPSIS
BG Builders - Lesley Roth: Recover deleted items (last 10 days) and review inbox rules
.DESCRIPTION
1. Connects to Exchange Online as sysadmin@bgbuildersllc.com
2. Recovers all soft-deleted items from Lesley's mailbox (last 10 days)
3. Lists all inbox rules on the account
.NOTES
Run in PowerShell 7 (pwsh) for best compatibility
Tenant: bgbuildersllc.com / sonorangreenllc.onmicrosoft.com
Target: lesley@bgbuildersllc.com
#>
$ErrorActionPreference = 'Stop'
$targetUser = 'lesley@bgbuildersllc.com'
# ── Connect to Exchange Online ──────────────────────────────────────
Write-Host "`n=== Connecting to Exchange Online ===" -ForegroundColor Cyan
try {
$session = Get-ConnectionInformation -ErrorAction SilentlyContinue
if (-not $session -or $session.State -ne 'Connected') {
Connect-ExchangeOnline -UserPrincipalName sysadmin@bgbuildersllc.com -ShowBanner:$false
} else {
Write-Host "Already connected to Exchange Online" -ForegroundColor Green
}
} catch {
Write-Host "Connecting fresh..." -ForegroundColor Yellow
Connect-ExchangeOnline -UserPrincipalName sysadmin@bgbuildersllc.com -ShowBanner:$false
}
# ── Part 1: Review Inbox Rules ──────────────────────────────────────
Write-Host "`n=== INBOX RULES for $targetUser ===" -ForegroundColor Cyan
try {
$rules = Get-InboxRule -Mailbox $targetUser -IncludeHidden
if ($rules) {
Write-Host "`nFound $($rules.Count) rule(s):" -ForegroundColor Yellow
foreach ($rule in $rules) {
Write-Host "`n--- Rule: $($rule.Name) ---" -ForegroundColor White
Write-Host " Enabled: $($rule.Enabled)"
Write-Host " Priority: $($rule.Priority)"
Write-Host " Description: $($rule.Description)"
if ($rule.ForwardTo) {
Write-Host " ** FORWARD TO: $($rule.ForwardTo)" -ForegroundColor Red
}
if ($rule.ForwardAsAttachmentTo) {
Write-Host " ** FWD ATTACH: $($rule.ForwardAsAttachmentTo)" -ForegroundColor Red
}
if ($rule.RedirectTo) {
Write-Host " ** REDIRECT TO: $($rule.RedirectTo)" -ForegroundColor Red
}
if ($rule.DeleteMessage) {
Write-Host " ** DELETE MSG: True" -ForegroundColor Red
}
if ($rule.MoveToFolder) {
Write-Host " Move To: $($rule.MoveToFolder)"
}
if ($rule.From) {
Write-Host " From: $($rule.From)"
}
if ($rule.SubjectContainsWords) {
Write-Host " Subject Words: $($rule.SubjectContainsWords -join ', ')"
}
if ($rule.BodyContainsWords) {
Write-Host " Body Words: $($rule.BodyContainsWords -join ', ')"
}
}
} else {
Write-Host "No inbox rules found." -ForegroundColor Green
}
} catch {
Write-Host "Error getting inbox rules: $_" -ForegroundColor Red
}
# ── Check forwarding configuration ──────────────────────────────────
Write-Host "`n=== FORWARDING CONFIG for $targetUser ===" -ForegroundColor Cyan
try {
$mbx = Get-Mailbox -Identity $targetUser
if ($mbx.ForwardingAddress) {
Write-Host " ForwardingAddress: $($mbx.ForwardingAddress)" -ForegroundColor Red
} else {
Write-Host " ForwardingAddress: (none)" -ForegroundColor Green
}
if ($mbx.ForwardingSmtpAddress) {
Write-Host " ForwardingSmtpAddress: $($mbx.ForwardingSmtpAddress)" -ForegroundColor Red
} else {
Write-Host " ForwardingSmtpAddress: (none)" -ForegroundColor Green
}
Write-Host " DeliverToMailboxAndForward: $($mbx.DeliverToMailboxAndForward)"
} catch {
Write-Host "Error getting forwarding config: $_" -ForegroundColor Red
}
# ── Part 2: Recover Deleted Items (last 10 days) ───────────────────
Write-Host "`n=== RECOVERING DELETED ITEMS (last 10 days) ===" -ForegroundColor Cyan
Write-Host "Target: $targetUser" -ForegroundColor White
$startDate = (Get-Date).AddDays(-10)
$endDate = Get-Date
$dateRange = "$($startDate.ToString('yyyy-MM-dd'))..$($endDate.ToString('yyyy-MM-dd'))"
# Step 1: Try Get-RecoverableItems (requires Mailbox Import Export role)
Write-Host "`n--- Method 1: Get-RecoverableItems ---" -ForegroundColor White
try {
Write-Host "Scanning recoverable items from $dateRange..."
$preview = Get-RecoverableItems -Identity $targetUser -FilterStartTime $startDate -FilterEndTime $endDate -FilterItemType All
if ($preview) {
Write-Host "Found $($preview.Count) recoverable item(s):" -ForegroundColor Yellow
$preview | Group-Object ItemClass | ForEach-Object { Write-Host " $($_.Name): $($_.Count) items" }
$preview | Select-Object -First 20 | ForEach-Object {
$subj = if ($_.Subject) { $_.Subject } else { "(no subject)" }
Write-Host " [$($_.LastModifiedTime.ToString('MM/dd HH:mm'))] $subj"
}
Write-Host "`nRestoring all $($preview.Count) items..." -ForegroundColor Yellow
Restore-RecoverableItems -Identity $targetUser -FilterStartTime $startDate -FilterEndTime $endDate -FilterItemType All -Confirm:$false
Write-Host "Recovery complete!" -ForegroundColor Green
} else {
Write-Host "No recoverable items found." -ForegroundColor Green
}
} catch {
Write-Host "Get-RecoverableItems not available (needs Mailbox Import Export role)." -ForegroundColor Yellow
Write-Host "Falling back to Compliance Search..." -ForegroundColor Yellow
# Step 2: Connect to Security & Compliance and run a content search
Write-Host "`n--- Method 2: Compliance Search (eDiscovery) ---" -ForegroundColor White
try {
Connect-IPPSSession -UserPrincipalName sysadmin@bgbuildersllc.com -ShowBanner:$false
Write-Host "Connected to Security & Compliance Center." -ForegroundColor Green
$searchName = "LesleyRecovery_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
$kql = "received>=$($startDate.ToString('yyyy-MM-dd')) AND received<=$($endDate.ToString('yyyy-MM-dd'))"
Write-Host "Creating compliance search: $searchName"
Write-Host " KQL: $kql"
Write-Host " Mailbox: $targetUser"
New-ComplianceSearch -Name $searchName `
-ExchangeLocation $targetUser `
-ContentMatchQuery $kql `
-Description "Recover deleted items for Lesley Roth - last 10 days" |
Out-Null
Write-Host "Starting search..." -ForegroundColor Yellow
Start-ComplianceSearch -Identity $searchName
# Poll for completion (max 5 minutes)
$maxWait = 300
$elapsed = 0
do {
Start-Sleep -Seconds 10
$elapsed += 10
$status = (Get-ComplianceSearch -Identity $searchName).Status
Write-Host " Status: $status ($elapsed sec)"
} while ($status -ne 'Completed' -and $elapsed -lt $maxWait)
$result = Get-ComplianceSearch -Identity $searchName
Write-Host "`nSearch Results:" -ForegroundColor Cyan
Write-Host " Status: $($result.Status)"
Write-Host " Items Found: $($result.Items)"
Write-Host " Size: $($result.Size)"
Write-Host " Success Results: $($result.SuccessResults)"
if ($result.Items -gt 0) {
Write-Host "`nItems found! To restore them:" -ForegroundColor Yellow
Write-Host " Option A: Use the Microsoft Purview portal > Content Search > '$searchName' > Export/Restore"
Write-Host " Option B: Run New-ComplianceSearchAction -SearchName '$searchName' -Purge -PurgeType SoftDelete"
Write-Host " (This moves items - for restore, use the Purview portal export instead)"
Write-Host "`n Purview URL: https://compliance.microsoft.com/contentsearchv2" -ForegroundColor Cyan
} else {
Write-Host "`nNo deleted items found in date range." -ForegroundColor Green
Write-Host "(Litigation hold preserves items in-place - they may still be in the mailbox)"
}
} catch {
Write-Host "Compliance search also failed: $_" -ForegroundColor Red
Write-Host "`nManual recovery options:" -ForegroundColor Yellow
Write-Host " 1. Outlook > Deleted Items > 'Recover items recently removed from this folder'"
Write-Host " (Log in as Barry/Shelly who have FullAccess)"
Write-Host " 2. CIPP > Mailbox Restore"
Write-Host " 3. Microsoft Purview portal > eDiscovery > Content Search"
Write-Host " URL: https://compliance.microsoft.com/contentsearchv2"
}
}
Write-Host "`n=== DONE ===" -ForegroundColor Cyan
Write-Host "Summary:"
Write-Host " - Inbox rules reviewed"
Write-Host " - Forwarding config checked"
Write-Host " - Deleted item recovery attempted"
Write-Host ""

View File

@@ -0,0 +1,71 @@
# BG Builders - Verify Lesley Device Wipe Status
$ErrorActionPreference = "Stop"
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
$lesleyUPN = "lesley@bgbuildersllc.com"
Write-Output "========================================="
Write-Output " Verify Device Wipe - Lesley Roth"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- Check Intune Managed Devices ---
Write-Output "`n[CHECK 1] Intune Managed Devices..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.DeviceManagement
Connect-MgGraph -TenantId $tenantId -Scopes 'DeviceManagementManagedDevices.Read.All' -NoWelcome
Write-Output "[OK] Connected to Graph"
$devices = Get-MgDeviceManagementManagedDevice -Filter "userPrincipalName eq '$lesleyUPN'" 2>$null
if ($devices) {
foreach ($d in $devices) {
Write-Output ""
Write-Output " Device: $($d.DeviceName)"
Write-Output " OS: $($d.OperatingSystem) $($d.OsVersion)"
Write-Output " Compliance: $($d.ComplianceState)"
Write-Output " Management State: $($d.ManagementState)"
Write-Output " Last Sync: $($d.LastSyncDateTime)"
Write-Output " Device Action: $($d.DeviceActionResults | ForEach-Object { "$($_.ActionName): $($_.ActionState)" })"
}
} else {
Write-Output " [INFO] No Intune-managed devices found for $lesleyUPN"
}
Disconnect-MgGraph
# --- Check EAS Devices ---
Write-Output "`n[CHECK 2] Exchange ActiveSync Devices..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected to Exchange Online"
$easDevices = Get-MobileDevice -Mailbox $lesleyUPN 2>$null
if ($easDevices) {
foreach ($eas in $easDevices) {
$stats = Get-MobileDeviceStatistics -Identity $eas.Identity 2>$null
Write-Output ""
Write-Output " Device: $($eas.FriendlyName)"
Write-Output " Type: $($eas.DeviceType)"
Write-Output " OS: $($eas.DeviceOS)"
Write-Output " Access State: $($eas.DeviceAccessState)"
Write-Output " First Sync: $($eas.FirstSyncTime)"
if ($stats) {
Write-Output " Last Sync: $($stats.LastSuccessSync)"
Write-Output " Wipe Status: $($stats.DeviceWipeSentTime)"
Write-Output " Wipe Ack: $($stats.DeviceWipeAckTime)"
Write-Output " Status: $($stats.Status)"
}
}
} else {
Write-Output " [INFO] No EAS devices found for $lesleyUPN"
}
# --- Check account status ---
Write-Output "`n[CHECK 3] Account Status..."
$mbx = Get-Mailbox -Identity $lesleyUPN -ErrorAction SilentlyContinue
if ($mbx) {
Write-Output " Mailbox Type: $($mbx.RecipientTypeDetails)"
Write-Output " Litigation Hold: $($mbx.LitigationHoldEnabled)"
}
Disconnect-ExchangeOnline -Confirm:$false
Write-Output "`n[OK] Verification complete"

View File

@@ -0,0 +1,119 @@
# BG Builders - Re-enable Lesley Roth + Add Shelly Delegate
# lesley@bgbuildersllc.com - was terminated 2026-02-27
# Actions:
# 1. Unblock sign-in
# 2. Reassign license
# 3. Add Shelly@bgbuildersllc.com as delegate (FullAccess + SendAs)
# 4. Enable litigation hold (prevent email deletion)
$ErrorActionPreference = "Stop"
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
$lesleyUPN = "lesley@bgbuildersllc.com"
$shellyUPN = "Shelly@bgbuildersllc.com"
Write-Output "========================================="
Write-Output " BG Builders - Re-enable Lesley Roth"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- STEP 1: Connect to Microsoft Graph ---
Write-Output "`n[STEP 1] Connecting to Microsoft Graph..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
Connect-MgGraph -TenantId $tenantId -Scopes 'User.ReadWrite.All','Organization.Read.All' -NoWelcome
Write-Output "[OK] Connected to Graph"
$lesley = Get-MgUser -UserId $lesleyUPN -Property Id,DisplayName,AccountEnabled,AssignedLicenses
Write-Output "[INFO] Lesley current state: AccountEnabled=$($lesley.AccountEnabled)"
# --- STEP 2: Unblock sign-in ---
Write-Output "`n[STEP 2] Unblocking sign-in..."
Update-MgUser -UserId $lesley.Id -AccountEnabled:$true
Write-Output "[OK] Sign-in unblocked for Lesley Roth"
# --- STEP 3: Reassign license ---
Write-Output "`n[STEP 3] Reassigning license..."
# List available SKUs to find the right one
$skus = Get-MgSubscribedSku -All
Write-Output "Available licenses:"
foreach ($sku in $skus) {
$available = $sku.PrepaidUnits.Enabled - $sku.ConsumedUnits
Write-Output " $($sku.SkuPartNumber) - $available available of $($sku.PrepaidUnits.Enabled) total"
}
# Assign Exchange Online Plan 1 (EXCHANGESTANDARD) - cheapest option for mailbox access
$exoPlan = $skus | Where-Object { $_.SkuPartNumber -eq "EXCHANGESTANDARD" }
if ($exoPlan) {
$availableCount = $exoPlan.PrepaidUnits.Enabled - $exoPlan.ConsumedUnits
if ($availableCount -gt 0) {
Set-MgUserLicense -UserId $lesley.Id -AddLicenses @(@{SkuId = $exoPlan.SkuId}) -RemoveLicenses @()
Write-Output "[OK] Assigned Exchange Online Plan 1 ($availableCount were available)"
} else {
Write-Output "[WARNING] No Exchange Online Plan 1 licenses available, trying Business Standard..."
$bizStd = $skus | Where-Object { $_.SkuPartNumber -eq "O365_BUSINESS_PREMIUM" }
if ($bizStd) {
$availableCount = $bizStd.PrepaidUnits.Enabled - $bizStd.ConsumedUnits
if ($availableCount -gt 0) {
Set-MgUserLicense -UserId $lesley.Id -AddLicenses @(@{SkuId = $bizStd.SkuId}) -RemoveLicenses @()
Write-Output "[OK] Assigned M365 Business Standard ($availableCount were available)"
} else {
Write-Output "[ERROR] No available licenses of either type - assign manually"
}
}
}
} else {
Write-Output "[WARNING] EXCHANGESTANDARD SKU not found, trying Business Standard..."
$bizStd = $skus | Where-Object { $_.SkuPartNumber -eq "O365_BUSINESS_PREMIUM" }
if ($bizStd) {
$availableCount = $bizStd.PrepaidUnits.Enabled - $bizStd.ConsumedUnits
if ($availableCount -gt 0) {
Set-MgUserLicense -UserId $lesley.Id -AddLicenses @(@{SkuId = $bizStd.SkuId}) -RemoveLicenses @()
Write-Output "[OK] Assigned M365 Business Standard ($availableCount were available)"
} else {
Write-Output "[ERROR] No available licenses - assign manually"
}
}
}
# --- STEP 4: Connect to Exchange Online ---
Write-Output "`n[STEP 4] Connecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected to Exchange Online"
# --- STEP 5: Add Shelly as delegate ---
Write-Output "`n[STEP 5] Adding Shelly as delegate on Lesley's mailbox..."
Add-MailboxPermission -Identity $lesleyUPN -User $shellyUPN -AccessRights FullAccess -AutoMapping $true
Write-Output "[OK] Shelly granted FullAccess (auto-mapped)"
Add-RecipientPermission -Identity $lesleyUPN -Trustee $shellyUPN -AccessRights SendAs -Confirm:$false
Write-Output "[OK] Shelly granted SendAs"
# --- STEP 6: Enable litigation hold ---
Write-Output "`n[STEP 6] Enabling litigation hold (prevent email deletion)..."
Set-Mailbox -Identity $lesleyUPN -LitigationHoldEnabled $true -LitigationHoldDuration Unlimited
Write-Output "[OK] Litigation hold enabled - emails cannot be permanently deleted"
# --- STEP 7: Verify ---
Write-Output "`n[STEP 7] Verifying permissions..."
$perms = Get-MailboxPermission -Identity $lesleyUPN | Where-Object { $_.User -notlike "NT AUTHORITY*" -and $_.User -notlike "S-1-*" }
Write-Output "Current mailbox permissions:"
foreach ($p in $perms) {
Write-Output " $($p.User) - $($p.AccessRights -join ', ')"
}
# --- DONE ---
Write-Output "`n========================================="
Write-Output " COMPLETE"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
Write-Output ""
Write-Output "Summary:"
Write-Output " [OK] Lesley sign-in re-enabled"
Write-Output " [OK] License reassigned"
Write-Output " [OK] Shelly has FullAccess + SendAs on Lesley's mailbox"
Write-Output " [OK] Litigation hold enabled - no email can be permanently deleted"
Write-Output " [INFO] Barry still has access from termination script"
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph

View File

@@ -0,0 +1,166 @@
# BG Builders - Employee Termination Script
# Employee: Lesley Roth (lesley@bgbuildersllc.com)
# Scheduled: 2026-02-27 12:00 PM MST
# Actions:
# 1. Block sign-in
# 2. Revoke all sessions
# 3. Reset password
# 4. Selective wipe company data from mobile devices
# 5. Convert mailbox to shared
# 6. Grant Barry full access + send-as on shared mailbox
# 7. Remove from Employees group
# 8. Hide from GAL
# 9. Grant Barry OneDrive access
# 10. Remove license
$ErrorActionPreference = "Stop"
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
$lesleyUPN = "lesley@bgbuildersllc.com"
$barryUPN = "barry@bgbuildersllc.com"
Write-Output "========================================="
Write-Output " BG Builders - Lesley Roth Termination"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- STEP 1: Connect to Microsoft Graph ---
Write-Output "`n[STEP 1] Connecting to Microsoft Graph..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
Import-Module Microsoft.Graph.Users.Actions
Import-Module Microsoft.Graph.Identity.DirectoryManagement
Connect-MgGraph -TenantId $tenantId -Scopes 'User.ReadWrite.All','Directory.ReadWrite.All','Group.ReadWrite.All','DeviceManagementManagedDevices.ReadWrite.All','DeviceManagementManagedDevices.PrivilegedOperations.All' -NoWelcome
Write-Output "[OK] Connected to Graph"
# Get user IDs
$lesley = Get-MgUser -UserId $lesleyUPN -Property Id,DisplayName,AccountEnabled,AssignedLicenses
$barry = Get-MgUser -UserId $barryUPN -Property Id,DisplayName
Write-Output "[OK] Lesley ID: $($lesley.Id)"
Write-Output "[OK] Barry ID: $($barry.Id)"
# --- STEP 2: Block sign-in ---
Write-Output "`n[STEP 2] Blocking sign-in..."
Update-MgUser -UserId $lesley.Id -AccountEnabled:$false
Write-Output "[OK] Sign-in blocked"
# --- STEP 3: Revoke all sessions ---
Write-Output "`n[STEP 3] Revoking all active sessions..."
Revoke-MgUserSignInSession -UserId $lesley.Id
Write-Output "[OK] All sessions revoked"
# --- STEP 4: Reset password ---
Write-Output "`n[STEP 4] Resetting password..."
$newPassword = -join ((65..90) + (97..122) + (48..57) + (33,35,36,37,38) | Get-Random -Count 24 | ForEach-Object {[char]$_})
$params = @{
passwordProfile = @{
forceChangePasswordNextSignIn = $true
password = $newPassword
}
}
Update-MgUser -UserId $lesley.Id -BodyParameter $params
Write-Output "[OK] Password reset (stored securely - not displayed)"
# --- STEP 5: Selective wipe company data from mobile devices ---
Write-Output "`n[STEP 5] Checking for managed mobile devices..."
Import-Module Microsoft.Graph.DeviceManagement
$devices = Get-MgDeviceManagementManagedDevice -Filter "userPrincipalName eq '$lesleyUPN'" 2>$null
if ($devices) {
foreach ($device in $devices) {
Write-Output " Found device: $($device.DeviceName) ($($device.OperatingSystem)) - ID: $($device.Id)"
Write-Output " Initiating selective wipe (company data only)..."
# Retire = selective wipe (removes company data, leaves personal data)
Invoke-MgRetireDeviceManagementManagedDevice -ManagedDeviceId $device.Id
Write-Output " [OK] Selective wipe initiated for $($device.DeviceName)"
}
Write-Output "[OK] All managed devices queued for selective wipe"
} else {
Write-Output "[INFO] No Intune-managed devices found"
Write-Output "[INFO] Checking for EAS (Exchange ActiveSync) devices..."
}
# --- STEP 6: Connect to Exchange Online and convert mailbox ---
Write-Output "`n[STEP 6] Connecting to Exchange Online..."
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
Write-Output "[OK] Connected to Exchange Online"
# Check for ActiveSync devices and wipe company data
$easDevices = Get-MobileDevice -Mailbox $lesleyUPN 2>$null
if ($easDevices) {
foreach ($eas in $easDevices) {
Write-Output " Found EAS device: $($eas.FriendlyName) ($($eas.DeviceOS))"
# AccountOnly wipe - removes only the M365 account, not personal data
Clear-MobileDevice -Identity $eas.Identity -AccountOnly -Confirm:$false
Write-Output " [OK] Account-only wipe initiated for $($eas.FriendlyName)"
}
Write-Output "[OK] All EAS devices queued for account wipe"
} else {
Write-Output "[INFO] No EAS mobile devices found"
}
Write-Output "`n[STEP 6a] Converting mailbox to shared..."
Set-Mailbox -Identity $lesleyUPN -Type Shared
Write-Output "[OK] Mailbox converted to shared"
# --- STEP 7: Grant Barry full access and send-as ---
Write-Output "`n[STEP 7] Granting Barry full access to shared mailbox..."
Add-MailboxPermission -Identity $lesleyUPN -User $barryUPN -AccessRights FullAccess -AutoMapping $true
Write-Output "[OK] Full access granted"
Write-Output "Granting Barry send-as permission..."
Add-RecipientPermission -Identity $lesleyUPN -Trustee $barryUPN -AccessRights SendAs -Confirm:$false
Write-Output "[OK] Send-as granted"
# --- STEP 8: Remove from Employees group ---
Write-Output "`n[STEP 8] Removing from Employees group..."
$employeesGroup = Get-MgGroup -Filter "displayName eq 'Employees'" | Select-Object -First 1
if ($employeesGroup) {
Remove-MgGroupMemberByRef -GroupId $employeesGroup.Id -DirectoryObjectId $lesley.Id -ErrorAction SilentlyContinue
Write-Output "[OK] Removed from Employees group ($($employeesGroup.Id))"
} else {
Write-Output "[WARNING] Employees group not found"
}
# --- STEP 9: Hide from GAL ---
Write-Output "`n[STEP 9] Hiding shared mailbox from Global Address List..."
Set-Mailbox -Identity $lesleyUPN -HiddenFromAddressListsEnabled $true
Write-Output "[OK] Hidden from GAL"
# --- STEP 10: Remove license ---
Write-Output "`n[STEP 10] Removing licenses..."
$licenses = $lesley.AssignedLicenses
if ($licenses.Count -gt 0) {
$licenseIds = $licenses | ForEach-Object { $_.SkuId }
Set-MgUserLicense -UserId $lesley.Id -AddLicenses @() -RemoveLicenses $licenseIds
Write-Output "[OK] Removed $($licenseIds.Count) license(s)"
} else {
Write-Output "[INFO] No licenses assigned"
}
# --- STEP 11: Grant Barry OneDrive access ---
Write-Output "`n[STEP 11] Granting Barry access to Lesley's OneDrive..."
# Note: OneDrive access delegation requires SharePoint admin or may need manual step
Write-Output "[WARNING] OneDrive access must be granted via M365 Admin Center:"
Write-Output " Admin Center > Users > Lesley Roth > OneDrive tab > Create link to files"
Write-Output " Or: SharePoint Admin > User Profiles > Manage User Profiles > Lesley Roth > Manage site collection owners > Add Barry"
# --- DONE ---
Write-Output "`n========================================="
Write-Output " TERMINATION COMPLETE"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
Write-Output ""
Write-Output "Summary:"
Write-Output " [OK] Sign-in blocked"
Write-Output " [OK] Sessions revoked"
Write-Output " [OK] Password reset"
Write-Output " [OK] Mobile devices - selective wipe initiated"
Write-Output " [OK] Mailbox converted to shared"
Write-Output " [OK] Barry has full access + send-as"
Write-Output " [OK] Removed from Employees group"
Write-Output " [OK] Hidden from GAL"
Write-Output " [OK] Licenses removed"
Write-Output " [WARNING] OneDrive access - manual step required"
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph

View File

@@ -0,0 +1,2 @@
@echo off
powershell.exe -ExecutionPolicy Bypass -File "D:\ClaudeTools\scripts\bgb-terminate-lesley.ps1" > "D:\ClaudeTools\scripts\bgb-terminate-lesley.log" 2>&1

View File

@@ -0,0 +1,16 @@
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
# Connect with interactive browser auth
Connect-MgGraph -TenantId 'ededa4fb-f6eb-4398-851d-5eb3e11fab27' -Scopes 'User.Read.All','User.ReadWrite.All','Directory.ReadWrite.All' -NoWelcome
# Find both users
$leslie = Get-MgUser -Filter "startsWith(displayName,'Leslie') or startsWith(mail,'leslie')" -Property DisplayName,Mail,UserPrincipalName,AccountEnabled,Id
$barry = Get-MgUser -Filter "startsWith(displayName,'Barry') or startsWith(mail,'barry')" -Property DisplayName,Mail,UserPrincipalName,AccountEnabled,Id
Write-Output '--- Leslie ---'
$leslie | Format-List DisplayName,Mail,UserPrincipalName,AccountEnabled,Id
Write-Output '--- Barry ---'
$barry | Format-List DisplayName,Mail,UserPrincipalName,AccountEnabled,Id
Disconnect-MgGraph

View File

@@ -0,0 +1,141 @@
# CIPP - Add Claude-MSP-Access as Auto-Consent App Template
# This adds Claude's app to CIPP so it gets automatically consented
# when you add new tenants via CIPP.
#
# Uses the CIPP API (ClaudeCipp2 credentials)
$ErrorActionPreference = "Stop"
$cippUrl = "https://cippcanvb.azurewebsites.net"
$cippTenantId = "ce61461e-81a0-4c84-bb4a-7b354a9a356d"
$cippClientId = "420cb849-542d-4374-9cb2-3d8ae0e1835b"
$cippClientSecret = "MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT"
$cippScope = "api://420cb849-542d-4374-9cb2-3d8ae0e1835b/.default"
$claudeAppId = "fabb3421-8b34-484b-bc17-e46de9703418"
Write-Output "========================================="
Write-Output " CIPP - Add Claude-MSP-Access Template"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- STEP 1: Get CIPP API token ---
Write-Output "`n[STEP 1] Getting CIPP API token..."
$tokenBody = @{
client_id = $cippClientId
client_secret = $cippClientSecret
scope = $cippScope
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$cippTenantId/oauth2/v2.0/token" -Method POST -Body $tokenBody
$token = $tokenResponse.access_token
Write-Output "[OK] Got CIPP API token"
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
# --- STEP 2: Check existing app approval templates ---
Write-Output "`n[STEP 2] Checking existing app approval templates..."
try {
$existing = Invoke-RestMethod -Uri "$cippUrl/api/ExecAppPermissionTemplate" -Headers $headers -Method GET
Write-Output "[INFO] Found $($existing.Count) existing template(s)"
foreach ($tmpl in $existing) {
Write-Output " - $($tmpl.displayName) ($($tmpl.appId))"
}
} catch {
Write-Output "[INFO] No existing templates or endpoint returned error: $($_.Exception.Message)"
}
# --- STEP 3: Add Claude-MSP-Access as app template ---
Write-Output "`n[STEP 3] Adding Claude-MSP-Access app template..."
# Application permissions Claude needs consented in each customer tenant
$appPermissions = @(
"User.ReadWrite.All",
"Directory.ReadWrite.All",
"Mail.ReadWrite",
"MailboxSettings.ReadWrite",
"AuditLog.Read.All",
"Application.ReadWrite.All",
"DelegatedPermissionGrant.ReadWrite.All",
"Group.ReadWrite.All",
"GroupMember.ReadWrite.All",
"SecurityEvents.ReadWrite.All",
"SecurityEvents.Read.All",
"SecurityIncident.ReadWrite.All",
"AppRoleAssignment.ReadWrite.All",
"UserAuthenticationMethod.ReadWrite.All",
"Organization.ReadWrite.All",
"Domain.Read.All",
"Policy.Read.All",
"Policy.ReadWrite.ConditionalAccess",
"Policy.ReadWrite.AuthenticationMethod",
"Policy.ReadWrite.AuthenticationFlows",
"Policy.ReadWrite.ApplicationConfiguration",
"Policy.ReadWrite.ConsentRequest",
"Policy.ReadWrite.CrossTenantAccess",
"Reports.Read.All",
"ReportSettings.ReadWrite.All",
"Device.ReadWrite.All",
"DeviceManagementApps.ReadWrite.All",
"DeviceManagementConfiguration.ReadWrite.All",
"DeviceManagementManagedDevices.ReadWrite.All",
"DeviceManagementManagedDevices.PrivilegedOperations.All",
"DeviceManagementRBAC.ReadWrite.All",
"DeviceManagementServiceConfig.ReadWrite.All",
"CrossTenantInformation.ReadBasic.All",
"Channel.Create",
"Channel.ReadBasic.All",
"ChannelMember.ReadWrite.All",
"Files.ReadWrite.All",
"Group.Create",
"InformationProtectionPolicy.Read.All",
"Place.Read.All",
"PrivilegedAccess.ReadWrite.AzureADGroup",
"SharePointTenantSettings.ReadWrite.All",
"Sites.FullControl.All",
"TeamMember.ReadWrite.All",
"TeamMember.ReadWriteNonOwnerRole.All",
"TeamsTelephoneNumber.ReadWrite.All"
)
$templateBody = @{
AppId = $claudeAppId
displayName = "Claude-MSP-Access (AI Investigation & Remediation)"
Permissions = $appPermissions
} | ConvertTo-Json -Depth 5
try {
$result = Invoke-RestMethod -Uri "$cippUrl/api/ExecAppPermissionTemplate" -Headers $headers -Method POST -Body $templateBody
Write-Output "[OK] Template added: $($result | ConvertTo-Json -Compress)"
} catch {
$errBody = $_.ErrorDetails.Message
Write-Output "[WARNING] API response: $errBody"
Write-Output "[INFO] If the endpoint doesn't support POST, you can add the template manually:"
Write-Output " CIPP > Settings > Application Approval > Add Application"
Write-Output " App ID: $claudeAppId"
Write-Output " Name: Claude-MSP-Access (AI Investigation & Remediation)"
Write-Output ""
Write-Output "Or use the CIPP UI to navigate to:"
Write-Output " Tenant Administration > Application Approval"
Write-Output " Click 'Add App' and enter the App ID above"
}
# --- STEP 4: Summary ---
Write-Output "`n========================================="
Write-Output " TEMPLATE SETUP SUMMARY"
Write-Output "========================================="
Write-Output ""
Write-Output "App ID: $claudeAppId"
Write-Output "Name: Claude-MSP-Access (AI Investigation & Remediation)"
Write-Output "Perms: $($appPermissions.Count) application permissions"
Write-Output ""
Write-Output "What happens now:"
Write-Output " 1. When you add a new tenant in CIPP, Claude's app gets auto-consented"
Write-Output " 2. For existing tenants, run CPV Refresh in CIPP to push the permissions"
Write-Output " 3. The admin consent URL also works as a manual fallback:"
Write-Output ""
Write-Output " https://login.microsoftonline.com/common/adminconsent?client_id=$claudeAppId&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient"
Write-Output ""

View File

@@ -0,0 +1,640 @@
{
"requiredResourceAccess": [
{
"resourceAppId": "c5393580-f805-4401-95e8-94b7a6ef2fc2",
"resourceAccess": [
{
"id": "594c1fb6-4f81-4475-ae41-0c394909246c",
"type": "Scope"
}
]
},
{
"resourceAppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85",
"resourceAccess": [
{
"id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9",
"type": "Role"
},
{
"id": "b0afded3-3588-46d8-8b3d-9842eff778da",
"type": "Role"
},
{
"id": "5e1e9171-754d-478c-812c-f1755a9a4c2d",
"type": "Role"
},
{
"id": "f3a65bd4-b703-46df-8f7e-0174fea562aa",
"type": "Role"
},
{
"id": "59a6b24b-4225-4393-8165-ebaec5f55d7a",
"type": "Role"
},
{
"id": "35930dcf-aceb-4bd1-b99a-8ffed403c974",
"type": "Role"
},
{
"id": "cac88765-0581-4025-9725-5ebc13f729ee",
"type": "Role"
},
{
"id": "1138cb37-bd11-4084-a2b7-9f71582aeddb",
"type": "Role"
},
{
"id": "78145de6-330d-4800-a6ce-494ff2d33d07",
"type": "Role"
},
{
"id": "9241abd9-d0e6-425a-bd4f-47ba86e767a4",
"type": "Role"
},
{
"id": "5b07b0dd-2377-4e44-a38d-703f09a0dc3c",
"type": "Role"
},
{
"id": "243333ab-4d21-40cb-a475-36241daa0842",
"type": "Role"
},
{
"id": "e330c4f0-4170-414e-a55a-2f022ec2b57b",
"type": "Role"
},
{
"id": "9255e99d-faf5-445e-bbf7-cb71482737c4",
"type": "Role"
},
{
"id": "8b9d79d0-ad75-4566-8619-f7500ecfcebe",
"type": "Scope"
},
{
"id": "5ac13192-7ace-4fcf-b828-1a26f28068ee",
"type": "Role"
},
{
"id": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
"type": "Role"
},
{
"id": "dbb9058a-0e50-45d7-ae91-66909b5d4664",
"type": "Role"
},
{
"id": "75359482-378d-4052-8f01-80520e7db3cd",
"type": "Role"
},
{
"id": "bf7b1a76-6e77-406b-b258-bf5c7720e98f",
"type": "Role"
},
{
"id": "62a82d76-70ea-41e2-9197-370581804d09",
"type": "Role"
},
{
"id": "dbaae8cf-10b5-4b86-a4a1-f871c94c6695",
"type": "Role"
},
{
"id": "19da66cb-0fb0-4390-b071-ebc76a349482",
"type": "Role"
},
{
"id": "6931bccd-447a-43d1-b442-00a195474933",
"type": "Role"
},
{
"id": "292d869f-3427-49a8-9dab-8c70152b74e9",
"type": "Role"
},
{
"id": "2cb92fee-97a3-4034-8702-24a6f5d0d1e9",
"type": "Role"
},
{
"id": "b6890674-9dd5-4e42-bb15-5af07f541ae1",
"type": "Role"
},
{
"id": "913b9306-0ce1-42b8-9137-6a7df690a760",
"type": "Role"
},
{
"id": "246dd0d5-5bd0-4def-940b-0421030a5b68",
"type": "Role"
},
{
"id": "be74164b-cff1-491c-8741-e671cb536e13",
"type": "Role"
},
{
"id": "25f85f3c-f66c-4205-8cd5-de92dd7f0cec",
"type": "Role"
},
{
"id": "29c18626-4985-4dcd-85c0-193eef327366",
"type": "Role"
},
{
"id": "01c0a623-fc9b-48e9-b794-0756f8e8f067",
"type": "Role"
},
{
"id": "999f8c63-0a38-4f1b-91fd-ed1947bdd1a9",
"type": "Role"
},
{
"id": "338163d7-f101-4c92-94ba-ca46fe52447c",
"type": "Role"
},
{
"id": "2f6817f8-7b12-4f0f-bc18-eeaf60705a9e",
"type": "Role"
},
{
"id": "230c1aed-a721-4c5d-9cb4-a90514e508ef",
"type": "Role"
},
{
"id": "2a60023f-3219-47ad-baa4-40e17cd02a1d",
"type": "Role"
},
{
"id": "025d3225-3f02-4882-b4c0-cd5b541a4e80",
"type": "Role"
},
{
"id": "04c55753-2244-4c25-87fc-704ab82a4f69",
"type": "Role"
},
{
"id": "bf394140-e372-4bf9-a898-299cfc7564e5",
"type": "Role"
},
{
"id": "34bf0e97-1971-4929-b999-9e2442d941d7",
"type": "Role"
},
{
"id": "19b94e34-907c-4f43-bde9-38b1909ed408",
"type": "Role"
},
{
"id": "a82116e5-55eb-4c41-a434-62fe8a61c773",
"type": "Role"
},
{
"id": "0121dc95-1b9f-4aed-8bac-58c5ac466691",
"type": "Role"
},
{
"id": "4437522e-9a86-4a41-a7da-e380edd4a97d",
"type": "Role"
},
{
"id": "741f803b-c850-494e-b5df-cde7c675a1ca",
"type": "Role"
},
{
"id": "50483e42-d915-4231-9639-7fdb7fd190e5",
"type": "Role"
},
{
"id": "bdfbf15f-ee85-4955-8675-146e8e5296b5",
"type": "Scope"
},
{
"id": "84bccea3-f856-4a8a-967b-dbe0a3d53a64",
"type": "Scope"
},
{
"id": "e4c9e354-4dc5-45b8-9e7c-e1393b0b1a20",
"type": "Scope"
},
{
"id": "b27a61ec-b99c-4d6a-b126-c4375d08ae30",
"type": "Scope"
},
{
"id": "101147cf-4178-4455-9d58-02b5c164e759",
"type": "Scope"
},
{
"id": "cc83893a-e232-4723-b5af-bd0b01bcfe65",
"type": "Scope"
},
{
"id": "9d8982ae-4365-4f57-95e9-d6032a4c0b87",
"type": "Scope"
},
{
"id": "2eadaff8-0bce-4198-a6b9-2cfc35a30075",
"type": "Scope"
},
{
"id": "0c3e411a-ce45-4cd1-8f30-f99a3efa7b11",
"type": "Scope"
},
{
"id": "2b61aa8a-6d36-4b2f-ac7b-f29867937c53",
"type": "Scope"
},
{
"id": "767156cb-16ae-4d10-8f8b-41b657c8c8c8",
"type": "Scope"
},
{
"id": "ebf0f66e-9fb1-49e4-a278-222f76911cf4",
"type": "Scope"
},
{
"id": "d649fb7c-72b4-4eec-b2b4-b15acf79e378",
"type": "Scope"
},
{
"id": "f3bfad56-966e-4590-a536-82ecf548ac1e",
"type": "Scope"
},
{
"id": "885f682f-a990-4bad-a642-36736a74b0c7",
"type": "Scope"
},
{
"id": "41ce6ca6-6826-4807-84f1-1c82854f7ee5",
"type": "Scope"
},
{
"id": "bac3b9c2-b516-4ef4-bd3b-c2ef73d8d804",
"type": "Scope"
},
{
"id": "11d4cd79-5ba5-460f-803f-e22c8ab85ccd",
"type": "Scope"
},
{
"id": "951183d1-1a61-466f-a6d1-1fde911bfd95",
"type": "Scope"
},
{
"id": "280b3b69-0437-44b1-bc20-3b2fca1ee3e9",
"type": "Scope"
},
{
"id": "7b3f05d5-f68c-4b8d-8c59-a2ecd12f24af",
"type": "Scope"
},
{
"id": "0883f392-0a7a-443d-8c76-16a6d39c7b63",
"type": "Scope"
},
{
"id": "3404d2bf-2b13-457e-a330-c24615765193",
"type": "Scope"
},
{
"id": "44642bfe-8385-4adc-8fc6-fe3cb2c375c3",
"type": "Scope"
},
{
"id": "0c5e8a55-87a6-4556-93ab-adc52c4d862d",
"type": "Scope"
},
{
"id": "662ed50a-ac44-4eef-ad86-62eed9be2a29",
"type": "Scope"
},
{
"id": "0e263e50-5827-48a4-b97c-d940288653c7",
"type": "Scope"
},
{
"id": "c5366453-9fb0-48a5-a156-24f0c49a4b84",
"type": "Scope"
},
{
"id": "2f9ee017-59c1-4f1d-9472-bd5529a7b311",
"type": "Scope"
},
{
"id": "4e46008b-f24c-477d-8fff-7bb4ec7aafe0",
"type": "Scope"
},
{
"id": "f81125ac-d3b7-4573-a3b2-7099cc39df9e",
"type": "Scope"
},
{
"id": "9e4862a5-b68f-479e-848a-4e07e25c9916",
"type": "Scope"
},
{
"id": "bb6f654c-d7fd-4ae3-85c3-fc380934f515",
"type": "Scope"
},
{
"id": "e0a7cdbb-08b0-4697-8264-0069786e9674",
"type": "Scope"
},
{
"id": "e383f46e-2787-4529-855e-0e479a3ffac0",
"type": "Scope"
},
{
"id": "a367ab51-6b49-43bf-a716-a1fb06d2a174",
"type": "Scope"
},
{
"id": "818c620a-27a9-40bd-a6a5-d96f7d610b4b",
"type": "Scope"
},
{
"id": "f6a3db3e-f7e8-4ed2-a414-557c8c9830be",
"type": "Scope"
},
{
"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
"type": "Scope"
},
{
"id": "37f7f235-527c-4136-accd-4a02d197296e",
"type": "Scope"
},
{
"id": "46ca0847-7e6b-426e-9775-ea810a948356",
"type": "Scope"
},
{
"id": "346c19ff-3fb2-4e81-87a0-bac9e33990c1",
"type": "Scope"
},
{
"id": "e67e6727-c080-415e-b521-e3f35d5248e9",
"type": "Scope"
},
{
"id": "4c06a06a-098a-4063-868e-5dfee3827264",
"type": "Scope"
},
{
"id": "572fea84-0151-49b2-9301-11cb16974376",
"type": "Scope"
},
{
"id": "b27add92-efb2-4f16-84f5-8108ba77985c",
"type": "Scope"
},
{
"id": "edb72de9-4252-4d03-a925-451deef99db7",
"type": "Scope"
},
{
"id": "7e823077-d88e-468f-a337-e18f1f0e6c7c",
"type": "Scope"
},
{
"id": "edd3c878-b384-41fd-95ad-e7407dd775be",
"type": "Scope"
},
{
"id": "ad902697-1014-4ef5-81ef-2b4301988e8c",
"type": "Scope"
},
{
"id": "4d135e65-66b8-41a8-9f8b-081452c91774",
"type": "Scope"
},
{
"id": "40b534c3-9552-4550-901b-23879c90bcf9",
"type": "Scope"
},
{
"id": "a8ead177-1889-4546-9387-f25e658e2a79",
"type": "Scope"
},
{
"id": "a84a9652-ffd3-496e-a991-22ba5529156a",
"type": "Scope"
},
{
"id": "14dad69e-099b-42c9-810b-d002981feec1",
"type": "Scope"
},
{
"id": "02e97553-ed7b-43d0-ab3c-f8bace0d040c",
"type": "Scope"
},
{
"id": "b955410e-7715-4a88-a940-dfd551018df3",
"type": "Scope"
},
{
"id": "d01b97e9-cbc0-49fe-810a-750afd5527a3",
"type": "Scope"
},
{
"id": "dc38509c-b87d-4da0-bd92-6bec988bac4a",
"type": "Scope"
},
{
"id": "6aedf524-7e1c-45a7-bd76-ded8cab8d0fc",
"type": "Scope"
},
{
"id": "128ca929-1a19-45e6-a3b8-435ec44a36ba",
"type": "Scope"
},
{
"id": "55896846-df78-47a7-aa94-8d3d4442ca7f",
"type": "Scope"
},
{
"id": "eda39fa6-f8cf-4c3c-a909-432c683e4c9b",
"type": "Scope"
},
{
"id": "aa07f155-3612-49b8-a147-6c590df35536",
"type": "Scope"
},
{
"id": "89fe6a52-be36-487e-b7d8-d061c450a026",
"type": "Scope"
},
{
"id": "7825d5d6-6049-4ce7-bdf6-3b8d53f4bcd0",
"type": "Scope"
},
{
"id": "485be79e-c497-4b35-9400-0e3fa7f2a5d4",
"type": "Scope"
},
{
"id": "4a06efd2-f825-4e34-813e-82a57b03d1ee",
"type": "Scope"
},
{
"id": "2104a4db-3a2f-4ea0-9dba-143d457dc666",
"type": "Scope"
},
{
"id": "0e755559-83fb-4b44-91d0-4cc721b9323e",
"type": "Scope"
},
{
"id": "39d65650-9d3e-4223-80db-a335590d027e",
"type": "Scope"
},
{
"id": "a9ff19c2-f369-4a95-9a25-ba9d460efc8e",
"type": "Scope"
},
{
"id": "b98bfd41-87c6-45cc-b104-e2de4f0dafb9",
"type": "Scope"
},
{
"id": "cac97e40-6730-457d-ad8d-4852fddab7ad",
"type": "Scope"
},
{
"id": "73e75199-7c3e-41bb-9357-167164dbb415",
"type": "Scope"
},
{
"id": "637d7bec-b31e-4deb-acc9-24275642a2c9",
"type": "Scope"
},
{
"id": "204e0828-b5ca-4ad8-b9f3-f32a958e7cc4",
"type": "Scope"
},
{
"id": "48971fc1-70d7-4245-af77-0beb29b53ee2",
"type": "Scope"
},
{
"id": "b7887744-6746-4312-813d-72daeaee7e2d",
"type": "Scope"
},
{
"id": "424b07a8-1209-4d17-9fe4-9018a93a1024",
"type": "Scope"
},
{
"id": "0a42382f-155c-4eb1-9bdc-21548ccaa387",
"type": "Role"
},
{
"id": "2d9bd318-b883-40be-9df7-63ec4fcdc424",
"type": "Role"
},
{
"id": "c8948c23-e66b-42db-83fd-770b71ab78d2",
"type": "Role"
},
{
"id": "a94a502d-0281-4d15-8cd2-682ac9362c4c",
"type": "Role"
},
{
"id": "e2a3a72e-5f79-4c64-b1b1-878b674786c9",
"type": "Role"
},
{
"id": "06b708a9-e830-4db3-a914-8e69da51d44f",
"type": "Role"
},
{
"id": "d903a879-88e0-4c09-b0c9-82f6a1333f84",
"type": "Role"
},
{
"id": "8e8e4742-1d95-4f68-9d56-6ee75648c72a",
"type": "Role"
}
]
},
{
"resourceAppId": "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd",
"resourceAccess": [
{
"id": "1cebfa2a-fb4d-419e-b5f9-839b4383e05a",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "dc50a0fb-09a3-484d-be87-e023b12c6440",
"type": "Role"
},
{
"id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99",
"type": "Role"
},
{
"id": "f9156939-25cd-4ba8-abfe-7fabcf003749",
"type": "Role"
},
{
"id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
"type": "Scope"
},
{
"id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415",
"type": "Scope"
},
{
"id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0",
"type": "Scope"
}
]
},
{
"resourceAppId": "48ac35b8-9aa8-4d74-927d-1f4a14a0b239",
"resourceAccess": [
{
"id": "e60370c1-e451-437e-aa6e-d76df38e5f15",
"type": "Scope"
}
]
},
{
"resourceAppId": "fc780465-2017-40d4-a0c5-307022471b92",
"resourceAccess": [
{
"id": "41269fc5-d04d-4bfd-bce7-43a51cea049a",
"type": "Role"
},
{
"id": "63a677ce-818c-4409-9d12-5c6d2e2a6bfe",
"type": "Scope"
}
]
}
]
}

View File

@@ -0,0 +1,188 @@
# Claude-MSP-Access - Automated Tenant Onboarding
# Onboards a customer tenant with full Claude + CIPP permissions
# No manual intervention required after initial admin consent
#
# Usage: .\claude-msp-onboard-tenant.ps1 -TenantDomain "sonorangreenllc.com"
#
# Prerequisites: Admin consent URL must be clicked first by customer/sysadmin:
# https://login.microsoftonline.com/common/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
#
# What this script does after consent:
# 1. Finds the Claude-MSP-Access service principal in the customer tenant
# 2. Activates Exchange Administrator directory role (if not active)
# 3. Assigns Exchange Administrator to Claude's SP (via CIPP Graph proxy)
# 4. Verifies all access: Graph, Exchange, Mail, Security, Intune
param(
[Parameter(Mandatory=$true)]
[string]$TenantDomain
)
$ErrorActionPreference = "Stop"
# --- Credentials ---
$cippUrl = "https://cippcanvb.azurewebsites.net"
$cippTenantId = "ce61461e-81a0-4c84-bb4a-7b354a9a356d"
$cippClientId = "420cb849-542d-4374-9cb2-3d8ae0e1835b"
$cippSecret = "MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT"
$claudeAppId = "fabb3421-8b34-484b-bc17-e46de9703418"
$claudeSecret = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
Write-Output "========================================="
Write-Output " Claude-MSP-Access - Tenant Onboarding"
Write-Output " Tenant: $TenantDomain"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- STEP 1: Get CIPP API token ---
Write-Output "`n[STEP 1] Getting CIPP API token..."
$tokenBody = @{
client_id = $cippClientId
client_secret = $cippSecret
scope = "api://$cippClientId/.default"
grant_type = "client_credentials"
}
$cippToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$cippTenantId/oauth2/v2.0/token" -Method POST -Body $tokenBody).access_token
$cippHeaders = @{ "Authorization" = "Bearer $cippToken" }
Write-Output "[OK] CIPP token acquired"
# --- STEP 2: Find Claude SP in customer tenant via CIPP ---
Write-Output "`n[STEP 2] Finding Claude-MSP-Access service principal..."
$spFilter = [uri]::EscapeDataString("appId eq '$claudeAppId'")
$spResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=servicePrincipals&`$filter=$spFilter" -Headers $cippHeaders
$sp = $spResult.Results | Select-Object -First 1
if (-not $sp) {
Write-Output "[ERROR] Claude-MSP-Access SP not found in $TenantDomain"
Write-Output "[INFO] Has admin consent been completed? Use this URL:"
Write-Output " https://login.microsoftonline.com/common/adminconsent?client_id=$claudeAppId&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient"
exit 1
}
$spId = $sp.id
Write-Output "[OK] Found SP: $($sp.displayName) (ID: $spId)"
# --- STEP 3: Get Exchange Administrator role ID ---
Write-Output "`n[STEP 3] Finding Exchange Administrator role..."
$rolesResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=directoryRoles" -Headers $cippHeaders
$exoRole = $rolesResult.Results | Where-Object { $_.displayName -eq "Exchange Administrator" }
if (-not $exoRole) {
Write-Output "[INFO] Exchange Admin role not activated, activating from template..."
# Exchange Administrator role template ID is always 29232cdf-9323-42fd-ade2-1d097af3e4de
$activateBody = [uri]::EscapeDataString((@{ roleTemplateId = "29232cdf-9323-42fd-ade2-1d097af3e4de" } | ConvertTo-Json -Compress))
$activateResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=directoryRoles&type=POST&body=$activateBody" -Headers $cippHeaders
# Re-fetch roles
$rolesResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=directoryRoles" -Headers $cippHeaders
$exoRole = $rolesResult.Results | Where-Object { $_.displayName -eq "Exchange Administrator" }
}
if (-not $exoRole) {
Write-Output "[ERROR] Could not find or activate Exchange Administrator role"
exit 1
}
$exoRoleId = $exoRole.id
Write-Output "[OK] Exchange Admin role: $exoRoleId"
# --- STEP 4: Assign Exchange Administrator to Claude SP ---
Write-Output "`n[STEP 4] Assigning Exchange Administrator role..."
$assignEndpoint = [uri]::EscapeDataString("directoryRoles/$exoRoleId/members/`$ref")
$assignBody = [uri]::EscapeDataString((@{ "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$spId" } | ConvertTo-Json -Compress))
try {
$assignResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=$assignEndpoint&type=POST&body=$assignBody" -Headers $cippHeaders
if ($assignResult.Results.CippStatus -eq "Good") {
Write-Output "[OK] Exchange Administrator assigned to Claude-MSP-Access"
} else {
Write-Output "[INFO] Assignment result: $($assignResult.Results | ConvertTo-Json -Compress)"
}
} catch {
$errMsg = $_.Exception.Message
if ($errMsg -match "already exist") {
Write-Output "[OK] Exchange Administrator already assigned"
} else {
Write-Output "[WARNING] Role assignment: $errMsg"
}
}
# --- STEP 5: Verify Claude API access ---
Write-Output "`n[STEP 5] Verifying Claude-MSP-Access API connectivity..."
# Get tenant ID from CIPP
$selectFields = [uri]::EscapeDataString("id,displayName")
$orgResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=organization&`$select=$selectFields" -Headers $cippHeaders
$customerTenantId = $orgResult.Results[0].id
Write-Output "[INFO] Tenant ID: $customerTenantId"
# Get Claude token for this tenant
$claudeTokenBody = @{
client_id = $claudeAppId
client_secret = $claudeSecret
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
try {
$claudeToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$customerTenantId/oauth2/v2.0/token" -Method POST -Body $claudeTokenBody).access_token
Write-Output "[OK] Claude Graph token acquired"
} catch {
Write-Output "[ERROR] Could not get Claude token - admin consent may not be complete"
Write-Output " $($_.Exception.Message)"
exit 1
}
$claudeHeaders = @{ "Authorization" = "Bearer $claudeToken"; "Content-Type" = "application/json" }
# Test endpoints
$tests = @(
@{ Name = "Users"; Uri = "https://graph.microsoft.com/v1.0/users?`$top=1&`$select=displayName" },
@{ Name = "Security"; Uri = "https://graph.microsoft.com/v1.0/security/alerts?`$top=1" },
@{ Name = "AuditLogs"; Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$top=1" },
@{ Name = "Policies"; Uri = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" },
@{ Name = "Devices"; Uri = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$top=1" }
)
foreach ($test in $tests) {
try {
$r = Invoke-RestMethod -Uri $test.Uri -Headers $claudeHeaders -ErrorAction Stop
Write-Output " [OK] $($test.Name)"
} catch {
$code = $_.Exception.Response.StatusCode.value__
Write-Output " [FAIL] $($test.Name): HTTP $code"
}
}
# Test Exchange Online REST
Write-Output "`n Testing Exchange Online REST API..."
try {
$exoTokenBody = @{
client_id = $claudeAppId
client_secret = $claudeSecret
scope = "https://outlook.office365.com/.default"
grant_type = "client_credentials"
}
$exoToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$customerTenantId/oauth2/v2.0/token" -Method POST -Body $exoTokenBody).access_token
$exoHeaders = @{ "Authorization" = "Bearer $exoToken"; "Content-Type" = "application/json" }
$invokeUrl = "https://outlook.office365.com/adminapi/beta/$customerTenantId/InvokeCommand"
$getMailbox = @{
CmdletInput = @{
CmdletName = "Get-Mailbox"
Parameters = @{ ResultSize = "1" }
}
} | ConvertTo-Json -Depth 5
$r = Invoke-RestMethod -Uri $invokeUrl -Headers $exoHeaders -Method POST -Body $getMailbox -ErrorAction Stop
Write-Output " [OK] Exchange Online (Get-Mailbox)"
} catch {
Write-Output " [FAIL] Exchange Online: $($_.Exception.Message)"
}
# --- DONE ---
Write-Output "`n========================================="
Write-Output " ONBOARDING COMPLETE: $TenantDomain"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
Write-Output ""
Write-Output "Claude-MSP-Access is fully operational for this tenant."
Write-Output "Capabilities: User mgmt, mail access, security alerts,"
Write-Output "audit logs, conditional access, Intune, Exchange admin,"
Write-Output "litigation hold, and all CIPP SAM operations."

View File

@@ -0,0 +1,93 @@
# Claude-MSP-Access - Update App Registration with Combined CIPP + Investigation Permissions
# App ID: fabb3421-8b34-484b-bc17-e46de9703418
# Partner Tenant: ce61461e-81a0-4c84-bb4a-7b354a9a356d
#
# This script updates the app registration to include:
# - All CIPP SAM required permissions (Graph, Exchange, SharePoint, Intune, PowerBI, Partner Center)
# - Claude investigation extras (Mail.ReadWrite, SecurityEvents.ReadWrite.All, etc.)
#
# After running this, the admin consent URL will grant everything in one click.
$ErrorActionPreference = "Stop"
$tenantId = "ce61461e-81a0-4c84-bb4a-7b354a9a356d"
$appId = "fabb3421-8b34-484b-bc17-e46de9703418"
Write-Output "========================================="
Write-Output " Claude-MSP-Access - Permission Update"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
# --- STEP 1: Connect to Graph ---
Write-Output "`n[STEP 1] Connecting to Microsoft Graph..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Applications
Connect-MgGraph -TenantId $tenantId -Scopes 'Application.ReadWrite.All' -NoWelcome
Write-Output "[OK] Connected to Graph"
# --- STEP 2: Get current app registration ---
Write-Output "`n[STEP 2] Reading current app registration..."
$app = Get-MgApplication -Filter "appId eq '$appId'"
if (-not $app) {
Write-Output "[ERROR] App not found: $appId"
exit 1
}
Write-Output "[OK] Found: $($app.DisplayName) (Object ID: $($app.Id))"
$currentPerms = ($app.RequiredResourceAccess | ForEach-Object { $_.ResourceAccess }).Count
Write-Output "[INFO] Current permission count: $currentPerms"
# --- STEP 3: Load combined manifest ---
Write-Output "`n[STEP 3] Loading combined permission manifest..."
$manifestPath = Join-Path $PSScriptRoot "claude-msp-combined-manifest.json"
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
# Build the requiredResourceAccess array
$resourceAccess = @()
foreach ($resource in $manifest.requiredResourceAccess) {
$accessList = @()
foreach ($access in $resource.resourceAccess) {
$accessList += @{
Id = $access.id
Type = $access.type
}
}
$resourceAccess += @{
ResourceAppId = $resource.resourceAppId
ResourceAccess = $accessList
}
}
$newPerms = ($manifest.requiredResourceAccess | ForEach-Object { $_.resourceAccess }).Count
Write-Output "[INFO] New permission count: $newPerms"
# --- STEP 4: Update app registration ---
Write-Output "`n[STEP 4] Updating app registration..."
Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess $resourceAccess
Write-Output "[OK] App registration updated with combined permissions"
# --- STEP 5: Verify ---
Write-Output "`n[STEP 5] Verifying update..."
$updated = Get-MgApplication -ApplicationId $app.Id
$updatedPerms = ($updated.RequiredResourceAccess | ForEach-Object { $_.ResourceAccess }).Count
Write-Output "[OK] Verified: $updatedPerms permissions across $($updated.RequiredResourceAccess.Count) resource APIs"
# --- STEP 6: Show admin consent URL ---
Write-Output "`n[STEP 6] Admin consent URL (use this to onboard tenants):"
Write-Output ""
Write-Output " https://login.microsoftonline.com/common/adminconsent?client_id=$appId&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient"
Write-Output ""
Write-Output "[INFO] This single URL now grants ALL permissions:"
Write-Output " - Microsoft Graph (application + delegated)"
Write-Output " - Exchange Online (ManageAsApp + Calendars + Mailbox)"
Write-Output " - SharePoint Online (FullControl)"
Write-Output " - Intune (user_impersonation)"
Write-Output " - PowerBI (Vulnerability.Read)"
Write-Output " - Partner Center (user_impersonation)"
Write-Output " - Office Management API (ActivityFeed.Read)"
Write-Output " - Claude investigation extras (Mail.ReadWrite, SecurityEvents.ReadWrite.All)"
Write-Output "`n========================================="
Write-Output " UPDATE COMPLETE"
Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "========================================="
Disconnect-MgGraph

View File

@@ -0,0 +1,68 @@
Write-Output "=== HKCU Excel Addins ==="
$path = "HKCU:\Software\Microsoft\Office\Excel\Addins"
if (Test-Path $path) {
Get-ChildItem $path | ForEach-Object {
Write-Output "`n Key: $($_.PSChildName)"
Get-ItemProperty $_.PSPath | Format-List
}
} else {
Write-Output " Path not found"
}
Write-Output "`n=== HKCU Word Addins ==="
$path = "HKCU:\Software\Microsoft\Office\Word\Addins"
if (Test-Path $path) {
Get-ChildItem $path | ForEach-Object {
Write-Output "`n Key: $($_.PSChildName)"
Get-ItemProperty $_.PSPath | Format-List
}
} else {
Write-Output " Path not found"
}
Write-Output "`n=== HKCU PowerPoint Addins ==="
$path = "HKCU:\Software\Microsoft\Office\PowerPoint\Addins"
if (Test-Path $path) {
Get-ChildItem $path | ForEach-Object {
Write-Output "`n Key: $($_.PSChildName)"
Get-ItemProperty $_.PSPath | Format-List
}
} else {
Write-Output " Path not found"
}
Write-Output "`n=== HKLM Excel Addins ==="
$path = "HKLM:\Software\Microsoft\Office\Excel\Addins"
if (Test-Path $path) {
Get-ChildItem $path | ForEach-Object {
Write-Output "`n Key: $($_.PSChildName)"
Get-ItemProperty $_.PSPath | Format-List
}
} else {
Write-Output " Path not found"
}
Write-Output "`n=== HKLM WOW6432 Excel Addins ==="
$path = "HKLM:\Software\WOW6432Node\Microsoft\Office\Excel\Addins"
if (Test-Path $path) {
Get-ChildItem $path | ForEach-Object {
Write-Output "`n Key: $($_.PSChildName)"
Get-ItemProperty $_.PSPath | Format-List
}
} else {
Write-Output " Path not found"
}
Write-Output "`n=== Search for any Datto/SmartBadge registry entries ==="
$results = reg query "HKCU\Software\Microsoft\Office" /s /f "Datto" 2>&1
$results | ForEach-Object { Write-Output $_ }
$results2 = reg query "HKLM\Software\Microsoft\Office" /s /f "Datto" 2>&1
$results2 | ForEach-Object { Write-Output $_ }
$results3 = reg query "HKLM\Software\WOW6432Node\Microsoft\Office" /s /f "SmartBadge" 2>&1
$results3 | ForEach-Object { Write-Output $_ }
Write-Output "`n=== SmartBadge DLL registration (CLSID) ==="
$results4 = reg query "HKLM\Software\Classes\CLSID" /s /f "SmartBadge" 2>&1
$results4 | Select-Object -First 20 | ForEach-Object { Write-Output $_ }
$results5 = reg query "HKCU\Software\Classes\CLSID" /s /f "SmartBadge" 2>&1
$results5 | Select-Object -First 20 | ForEach-Object { Write-Output $_ }

View File

@@ -0,0 +1,100 @@
Windows Registry Editor Version 5.00
; Datto SmartBadge Add-in Registration for 64-bit Office
; Generated from working installation reference
; === Excel Add-ins ===
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Excel\Addins\Datto.SmartBadgeShim]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Excel\Addins\Datto.SmartBadgeShim_CC]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
; === Word Add-ins ===
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Word\Addins\Datto.SmartBadgeShim]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Word\Addins\Datto.SmartBadgeShim_CC]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
; === PowerPoint Add-ins ===
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\PowerPoint\Addins\Datto.SmartBadgeShim]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\PowerPoint\Addins\Datto.SmartBadgeShim_CC]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
; === WOW6432Node (32-bit compatibility layer) ===
[HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Office\Excel\Addins\Datto.SmartBadgeShim]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Office\Excel\Addins\Datto.SmartBadgeShim_CC]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Office\Word\Addins\Datto.SmartBadgeShim]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Office\Word\Addins\Datto.SmartBadgeShim_CC]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Office\PowerPoint\Addins\Datto.SmartBadgeShim]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Office\PowerPoint\Addins\Datto.SmartBadgeShim_CC]
"FriendlyName"="Datto SmartBadge"
"Description"="SmartBadge for Microsoft Office applications."
"LoadBehavior"=dword:00000003
; === COM CLSID Registration (64-bit shim DLL) ===
[HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{2B96EDC1-FDF3-47E1-B177-F205E7B98DF4}]
@="Datto.SmartBadgeShim"
[HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{2B96EDC1-FDF3-47E1-B177-F205E7B98DF4}\InprocServer32]
@="C:\\Program Files\\Datto\\Workplace Desktop\\SmartBadge\\DattoSmartBadgeShim_x64.dll"
"ThreadingModel"="Both"
[HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{2B96EDC1-FDF3-47E1-B177-F205E7B98DF4}\ProgID]
@="Datto.SmartBadgeShim"
[HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{3C639243-95A2-400D-B4B4-4384DA7F61D3}]
@="Datto.SmartBadgeShim_CC"
[HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{3C639243-95A2-400D-B4B4-4384DA7F61D3}\InprocServer32]
@="C:\\Program Files\\Datto\\Workplace2\\SmartBadge\\DattoSmartBadgeShim_x64.dll"
"ThreadingModel"="Both"
[HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{3C639243-95A2-400D-B4B4-4384DA7F61D3}\ProgID]
@="Datto.SmartBadgeShim_CC"
; === Outlook Plugin (if needed) ===
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Outlook\Addins\Datto.OutlookPluginShim]
"FriendlyName"="Datto Outlook Plugin"
"Description"="Datto add-in for Microsoft Outlook."
"LoadBehavior"=dword:00000003
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Outlook\Addins\Datto.OutlookPluginShim_CC]
"FriendlyName"="Datto Outlook Plugin"
"Description"="Datto add-in for Microsoft Outlook."
"LoadBehavior"=dword:00000003

View File

@@ -0,0 +1,27 @@
Import-Module Posh-SSH
$secPassword = ConvertTo-SecureString 'Paper123!@#' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('INTRANET\sysadmin', $secPassword)
$session = New-SSHSession -ComputerName 192.168.0.6 -Credential $cred -AcceptKey -Force -ConnectionTimeout 30
Write-Output "[OK] Connected to AD2"
$portCheck = @'
powershell -Command "foreach ($p in @(22,445,3389,5985)) { $t = New-Object System.Net.Sockets.TcpClient; $r = $t.BeginConnect('192.168.0.149', $p, $null, $null); $w = $r.AsyncWaitHandle.WaitOne(2000, $false); if ($w -and $t.Connected) { Write-Output \"$p : Open\"; $t.Close() } else { Write-Output \"$p : Closed\"; $t.Close() } }"
'@
Write-Output "`n=== Port Check 192.168.0.149 ==="
$result = Invoke-SSHCommand -SessionId $session.SessionId -Command $portCheck -TimeOut 30
Write-Output $result.Output
# If 445 is open, try PsExec-style via SMB to check creds
# If 5985 not open, try enabling WinRM via scheduled task
$cmd = @'
powershell -Command "Invoke-Command -ComputerName DESKTOP-Q33I5H1 -Credential (New-Object PSCredential('INTRANET\sysadmin',(ConvertTo-SecureString 'Paper123!@#' -AsPlainText -Force))) -ScriptBlock { cmdkey /list } -ErrorAction SilentlyContinue 2>&1"
'@
Write-Output "`n=== WinRM attempt ==="
$r2 = Invoke-SSHCommand -SessionId $session.SessionId -Command $cmd -TimeOut 30
Write-Output $r2.Output
if ($r2.Error) { Write-Output $r2.Error }
Remove-SSHSession -SessionId $session.SessionId | Out-Null

View File

@@ -0,0 +1,62 @@
$secPassword = ConvertTo-SecureString 'Paper123!@#' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('INTRANET\sysadmin', $secPassword)
# Query lockout events from AD1 via AD2 (same subnet hop)
Invoke-Command -ComputerName 192.168.0.6 -Credential $cred -Authentication Negotiate -ScriptBlock {
# Query AD1's event log from AD2 (both on same subnet)
Write-Output "=== Lockout Events (4740) from AD1 ==="
try {
$lockouts = Get-WinEvent -ComputerName AD1 -FilterHashtable @{LogName='Security'; Id=4740; StartTime=(Get-Date).AddDays(-7)} -ErrorAction Stop |
Where-Object { $_.Properties[0].Value -eq 'jlohr' } |
Select-Object -First 30
foreach ($e in $lockouts) {
Write-Output "$($e.TimeCreated) | Caller: $($e.Properties[1].Value)"
}
if (-not $lockouts) { Write-Output " None found" }
} catch { Write-Output " ERROR: $_" }
Write-Output "`n=== Kerberos Failures (4771) from AD1 ==="
try {
$k = Get-WinEvent -ComputerName AD1 -FilterHashtable @{LogName='Security'; Id=4771; StartTime=(Get-Date).AddDays(-3)} -ErrorAction Stop |
Where-Object { $_.Properties[0].Value -eq 'jlohr' } |
Select-Object -First 30
foreach ($e in $k) {
Write-Output "$($e.TimeCreated) | IP: $($e.Properties[6].Value) | Status: $($e.Properties[4].Value)"
}
if (-not $k) { Write-Output " None found" }
} catch { Write-Output " ERROR: $_" }
Write-Output "`n=== NTLM Failures (4776) from AD1 ==="
try {
$n = Get-WinEvent -ComputerName AD1 -FilterHashtable @{LogName='Security'; Id=4776; StartTime=(Get-Date).AddDays(-3)} -ErrorAction Stop |
Where-Object { $_.Properties[1].Value -eq 'jlohr' -and $_.Properties[2].Value -ne 0 } |
Select-Object -First 30
foreach ($e in $n) {
Write-Output "$($e.TimeCreated) | Workstation: $($e.Properties[0].Value) | Error: $($e.Properties[2].Value)"
}
if (-not $n) { Write-Output " None found" }
} catch { Write-Output " ERROR: $_" }
Write-Output "`n=== Logon Failures (4625) from AD1 ==="
try {
$f = Get-WinEvent -ComputerName AD1 -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-3)} -ErrorAction Stop |
Where-Object { $_.Properties[5].Value -eq 'jlohr' } |
Select-Object -First 30
foreach ($e in $f) {
Write-Output "$($e.TimeCreated) | Source: $($e.Properties[13].Value) ($($e.Properties[19].Value)) | Type: $($e.Properties[10].Value) | Reason: $($e.Properties[8].Value)"
}
if (-not $f) { Write-Output " None found" }
} catch { Write-Output " ERROR: $_" }
# Also check AD2's own logs
Write-Output "`n=== Lockout Events (4740) from AD2 ==="
try {
$l2 = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4740; StartTime=(Get-Date).AddDays(-7)} -ErrorAction Stop |
Where-Object { $_.Properties[0].Value -eq 'jlohr' } |
Select-Object -First 30
foreach ($e in $l2) {
Write-Output "$($e.TimeCreated) | Caller: $($e.Properties[1].Value)"
}
if (-not $l2) { Write-Output " None found" }
} catch { Write-Output " ERROR: $_" }
} -ErrorAction Stop

27
scripts/df-test-winrm.ps1 Normal file
View File

@@ -0,0 +1,27 @@
$secPassword = ConvertTo-SecureString 'Paper123!@#' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('INTRANET\sysadmin', $secPassword)
Write-Output "Testing Negotiate auth..."
try {
$result = Invoke-Command -ComputerName 192.168.0.27 -Credential $cred -Authentication Negotiate -ScriptBlock { hostname } -ErrorAction Stop
Write-Output "[OK] Negotiate: $result"
} catch {
Write-Output "[FAIL] Negotiate: $_"
}
Write-Output "`nTesting Default auth..."
try {
$result = Invoke-Command -ComputerName 192.168.0.27 -Credential $cred -ScriptBlock { hostname } -ErrorAction Stop
Write-Output "[OK] Default: $result"
} catch {
Write-Output "[FAIL] Default: $_"
}
Write-Output "`nTesting with SessionOption..."
try {
$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$result = Invoke-Command -ComputerName 192.168.0.27 -Credential $cred -Authentication Negotiate -SessionOption $so -ScriptBlock { hostname } -ErrorAction Stop
Write-Output "[OK] SessionOption: $result"
} catch {
Write-Output "[FAIL] SessionOption: $_"
}

290
scripts/migration-pack.sh Normal file
View File

@@ -0,0 +1,290 @@
#!/bin/bash
###############################################################################
# migration-pack.sh
#
# Creates an encrypted migration archive of all non-git ClaudeTools data.
# Works in Git Bash on Windows AND native Linux bash.
#
# Usage: ./migration-pack.sh [source_dir]
# source_dir Path to ClaudeTools repo (default: script's parent directory)
#
# Output: claudetools-migration-YYYYMMDD.tar.gpg in current working directory
###############################################################################
set -euo pipefail
# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="${1:-"$(cd "$SCRIPT_DIR/.." && pwd)"}"
DATE_STAMP="$(date +%Y%m%d)"
ARCHIVE_NAME="claudetools-migration-${DATE_STAMP}.tar.gpg"
STAGING_DIR=""
MANIFEST_FILE="MIGRATION_MANIFEST.txt"
WARN_COUNT=0
COPY_COUNT=0
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log_info() { echo "[INFO] $*"; }
log_ok() { echo "[OK] $*"; }
log_warn() { echo "[WARNING] $*"; WARN_COUNT=$((WARN_COUNT + 1)); }
log_error() { echo "[ERROR] $*"; }
cleanup() {
if [[ -n "$STAGING_DIR" && -d "$STAGING_DIR" ]]; then
log_info "Cleaning up staging directory..."
rm -rf "$STAGING_DIR"
fi
}
trap cleanup EXIT
check_tool() {
if ! command -v "$1" &>/dev/null; then
log_error "Required tool not found: $1"
exit 1
fi
}
# Copy a single file into the staging area, preserving relative path.
# Warns and continues if the source does not exist.
stage_file() {
local rel_path="$1"
local src="${SOURCE_DIR}/${rel_path}"
local dst="${STAGING_DIR}/${rel_path}"
if [[ ! -e "$src" ]]; then
log_warn "File not found, skipping: ${rel_path}"
return
fi
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
COPY_COUNT=$((COPY_COUNT + 1))
log_ok "Staged: ${rel_path}"
}
# Copy an entire directory into the staging area.
stage_dir() {
local rel_path="$1"
local src="${SOURCE_DIR}/${rel_path}"
local dst="${STAGING_DIR}/${rel_path}"
if [[ ! -d "$src" ]]; then
log_warn "Directory not found, skipping: ${rel_path}"
return
fi
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
COPY_COUNT=$((COPY_COUNT + 1))
log_ok "Staged directory: ${rel_path}"
}
# Detect the Claude AI context directory based on platform conventions.
# On Windows (Git Bash), the repo path D:\ClaudeTools becomes D--ClaudeTools.
# On Linux/macOS, /home/user/ClaudeTools becomes -home-user-ClaudeTools.
detect_claude_context_dir() {
local claude_projects_base="${HOME}/.claude/projects"
if [[ ! -d "$claude_projects_base" ]]; then
echo ""
return
fi
# Try Windows-style mapping first: D:\ClaudeTools -> D--ClaudeTools
# Convert SOURCE_DIR from /d/path or D:/path to D--path
local win_name=""
if [[ "$SOURCE_DIR" =~ ^/([a-zA-Z])/(.*) ]]; then
# Git Bash path like /d/ClaudeTools
local drive="${BASH_REMATCH[1]^^}"
local rest="${BASH_REMATCH[2]}"
win_name="${drive}--${rest//\//-}"
elif [[ "$SOURCE_DIR" =~ ^([a-zA-Z]):(.*) ]]; then
# Windows path like D:\ClaudeTools or D:/ClaudeTools
local drive="${BASH_REMATCH[1]^^}"
local rest="${BASH_REMATCH[2]}"
rest="${rest//\\/-}"
rest="${rest//\//-}"
rest="${rest#-}"
win_name="${drive}--${rest}"
fi
if [[ -n "$win_name" && -d "${claude_projects_base}/${win_name}" ]]; then
echo "${claude_projects_base}/${win_name}"
return
fi
# Try Linux-style mapping: absolute path with slashes replaced by dashes
local linux_name="${SOURCE_DIR//\//-}"
linux_name="${linux_name#-}"
if [[ -d "${claude_projects_base}/${linux_name}" ]]; then
echo "${claude_projects_base}/${linux_name}"
return
fi
echo ""
}
# Write a manifest of everything in the staging directory.
write_manifest() {
local manifest="${STAGING_DIR}/${MANIFEST_FILE}"
{
echo "============================================================"
echo " ClaudeTools Migration Manifest"
echo " Created: $(date '+%Y-%m-%d %H:%M:%S')"
echo " Source: ${SOURCE_DIR}"
echo " Host: $(hostname)"
echo "============================================================"
echo ""
echo "Contents:"
echo "------------------------------------------------------------"
# Use find to list all files with sizes.
# On Git Bash, stat flags differ from GNU coreutils; use portable approach.
find "$STAGING_DIR" -type f ! -name "$MANIFEST_FILE" -print0 | while IFS= read -r -d '' file; do
local rel="${file#"${STAGING_DIR}/"}"
local size
size="$(wc -c < "$file" 2>/dev/null || echo "?")"
printf " %-60s %s bytes\n" "$rel" "$size"
done | sort
echo "------------------------------------------------------------"
echo ""
# Directory count and file count
local dir_count file_count total_size
dir_count="$(find "$STAGING_DIR" -mindepth 1 -type d | wc -l)"
file_count="$(find "$STAGING_DIR" -type f ! -name "$MANIFEST_FILE" | wc -l)"
total_size="$(find "$STAGING_DIR" -type f ! -name "$MANIFEST_FILE" -print0 | xargs -0 wc -c 2>/dev/null | tail -n1 | awk '{print $1}')"
echo "Directories: ${dir_count}"
echo "Files: ${file_count}"
echo "Total size: ${total_size:-0} bytes"
} > "$manifest"
log_ok "Manifest written: ${MANIFEST_FILE}"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
echo "============================================================"
echo " ClaudeTools Migration Packer"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================================"
echo ""
# Validate source directory
if [[ ! -d "$SOURCE_DIR" ]]; then
log_error "Source directory does not exist: ${SOURCE_DIR}"
exit 1
fi
log_info "Source directory: ${SOURCE_DIR}"
# Check required tools
check_tool tar
check_tool gpg
log_ok "Required tools available (tar, gpg)"
# Create staging directory
STAGING_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t 'migration')"
log_info "Staging directory: ${STAGING_DIR}"
echo ""
# ------------------------------------------------------------------
# Stage individual files
# ------------------------------------------------------------------
log_info "--- Staging individual files ---"
stage_file "credentials.md"
stage_file ".env"
stage_file ".mcp.json"
stage_file "dataforth-notifications-creds.txt"
stage_file ".claude/settings.local.json"
stage_file "projects/solverbot/.env"
stage_file "session-logs/2026-02-25-session.md"
echo ""
# ------------------------------------------------------------------
# Stage directories
# ------------------------------------------------------------------
log_info "--- Staging directories ---"
stage_dir "imported-conversations"
stage_dir "backups"
stage_dir "clients/gurushow"
echo ""
# ------------------------------------------------------------------
# Stage Claude AI context
# ------------------------------------------------------------------
log_info "--- Staging Claude AI context ---"
local claude_ctx
claude_ctx="$(detect_claude_context_dir)"
if [[ -n "$claude_ctx" && -d "$claude_ctx" ]]; then
local ctx_dst="${STAGING_DIR}/claude-context"
mkdir -p "$ctx_dst"
cp -a "$claude_ctx"/. "$ctx_dst/"
COPY_COUNT=$((COPY_COUNT + 1))
log_ok "Staged Claude context from: ${claude_ctx}"
else
log_warn "Claude AI context directory not found. Looked under \$HOME/.claude/projects/"
log_warn "You may need to manually copy this after migration."
fi
echo ""
# ------------------------------------------------------------------
# Write manifest
# ------------------------------------------------------------------
log_info "--- Writing manifest ---"
write_manifest
echo ""
# ------------------------------------------------------------------
# Create encrypted archive
# ------------------------------------------------------------------
log_info "--- Creating encrypted archive ---"
log_info "You will be prompted for a passphrase to encrypt the archive."
echo ""
# Create tar from staging contents, then encrypt with GPG symmetric.
# Use --batch only if GPG_PASSPHRASE env var is set (for automation).
local tar_tmp="${STAGING_DIR}.tar"
tar -cf "$tar_tmp" -C "$STAGING_DIR" .
if [[ -n "${GPG_PASSPHRASE:-}" ]]; then
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--symmetric --cipher-algo AES256 \
--output "$ARCHIVE_NAME" "$tar_tmp"
else
gpg --symmetric --cipher-algo AES256 \
--output "$ARCHIVE_NAME" "$tar_tmp"
fi
rm -f "$tar_tmp"
if [[ ! -f "$ARCHIVE_NAME" ]]; then
log_error "Archive creation failed."
exit 1
fi
local archive_size
archive_size="$(wc -c < "$ARCHIVE_NAME")"
echo ""
echo "============================================================"
echo " Migration Pack Complete"
echo "============================================================"
echo ""
echo " Archive: $(pwd)/${ARCHIVE_NAME}"
echo " Size: ${archive_size} bytes"
echo " Items: ${COPY_COUNT} files/directories staged"
echo " Warnings: ${WARN_COUNT}"
echo " Encrypted: AES-256 (GPG symmetric)"
echo ""
echo " To restore, run:"
echo " ./migration-restore.sh ${ARCHIVE_NAME}"
echo ""
log_ok "Done."
}
main "$@"

View File

@@ -0,0 +1,296 @@
#!/bin/bash
###############################################################################
# migration-restore.sh
#
# Restores a ClaudeTools environment from an encrypted migration archive.
# Works in Git Bash on Windows AND native Linux bash.
#
# Usage: ./migration-restore.sh <archive.tar.gpg> [target_dir]
# archive.tar.gpg Path to the encrypted migration archive
# target_dir Where to clone/restore (default: $HOME/ClaudeTools)
###############################################################################
set -euo pipefail
# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------
ARCHIVE_PATH="${1:-}"
TARGET_DIR="${2:-"${HOME}/ClaudeTools"}"
GITEA_REPO="ssh://git@172.16.3.20:2222/azcomputerguru/claudetools.git"
TEMP_EXTRACT=""
WARN_COUNT=0
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log_info() { echo "[INFO] $*"; }
log_ok() { echo "[OK] $*"; }
log_warn() { echo "[WARNING] $*"; WARN_COUNT=$((WARN_COUNT + 1)); }
log_error() { echo "[ERROR] $*"; }
cleanup() {
if [[ -n "$TEMP_EXTRACT" && -d "$TEMP_EXTRACT" ]]; then
log_info "Cleaning up temporary extraction directory..."
rm -rf "$TEMP_EXTRACT"
fi
}
trap cleanup EXIT
check_tool() {
local tool="$1"
if ! command -v "$tool" &>/dev/null; then
log_error "Required tool not found: ${tool}"
log_error "Please install ${tool} before running this script."
exit 1
fi
log_ok "Found: ${tool}"
}
# Derive the Claude projects directory name from the target path.
# On Windows (Git Bash): /d/ClaudeTools -> D--ClaudeTools
# On Linux: /home/user/ClaudeTools -> -home-user-ClaudeTools
derive_claude_project_name() {
local abs_target
abs_target="$(cd "$TARGET_DIR" && pwd)"
# Check if we are on Windows (Git Bash) by looking at path format
if [[ "$abs_target" =~ ^/([a-zA-Z])/(.*) ]]; then
# Git Bash path: /d/ClaudeTools -> D--ClaudeTools
local drive="${BASH_REMATCH[1]^^}"
local rest="${BASH_REMATCH[2]}"
echo "${drive}--${rest//\//-}"
elif [[ "$abs_target" =~ ^([a-zA-Z]):(.*) ]]; then
# Raw Windows path: D:\ClaudeTools -> D--ClaudeTools
local drive="${BASH_REMATCH[1]^^}"
local rest="${BASH_REMATCH[2]}"
rest="${rest//\\/-}"
rest="${rest//\//-}"
rest="${rest#-}"
echo "${drive}--${rest}"
else
# Linux/macOS: /home/user/ClaudeTools -> -home-user-ClaudeTools
local name="${abs_target//\//-}"
name="${name#-}"
echo "${name}"
fi
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
echo "============================================================"
echo " ClaudeTools Migration Restore"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================================"
echo ""
# ------------------------------------------------------------------
# Validate arguments
# ------------------------------------------------------------------
if [[ -z "$ARCHIVE_PATH" ]]; then
log_error "Usage: $0 <archive.tar.gpg> [target_dir]"
log_error " archive.tar.gpg Encrypted migration archive"
log_error " target_dir Restore location (default: \$HOME/ClaudeTools)"
exit 1
fi
if [[ ! -f "$ARCHIVE_PATH" ]]; then
log_error "Archive not found: ${ARCHIVE_PATH}"
exit 1
fi
log_info "Archive: ${ARCHIVE_PATH}"
log_info "Target dir: ${TARGET_DIR}"
echo ""
# ------------------------------------------------------------------
# Check required tools
# ------------------------------------------------------------------
log_info "--- Checking required tools ---"
check_tool git
check_tool gpg
check_tool tar
echo ""
# ------------------------------------------------------------------
# Decrypt archive
# ------------------------------------------------------------------
log_info "--- Decrypting archive ---"
log_info "You will be prompted for the passphrase."
echo ""
TEMP_EXTRACT="$(mktemp -d 2>/dev/null || mktemp -d -t 'migration-restore')"
local tar_tmp="${TEMP_EXTRACT}/archive.tar"
if [[ -n "${GPG_PASSPHRASE:-}" ]]; then
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--decrypt --output "$tar_tmp" "$ARCHIVE_PATH"
else
gpg --decrypt --output "$tar_tmp" "$ARCHIVE_PATH"
fi
if [[ ! -f "$tar_tmp" ]]; then
log_error "Decryption failed. Check your passphrase and try again."
exit 1
fi
log_ok "Archive decrypted."
echo ""
# ------------------------------------------------------------------
# Extract archive to temp location
# ------------------------------------------------------------------
log_info "--- Extracting archive ---"
local extract_dir="${TEMP_EXTRACT}/contents"
mkdir -p "$extract_dir"
tar -xf "$tar_tmp" -C "$extract_dir"
rm -f "$tar_tmp"
log_ok "Archive extracted."
# Show manifest if present
if [[ -f "${extract_dir}/MIGRATION_MANIFEST.txt" ]]; then
echo ""
log_info "--- Migration Manifest ---"
cat "${extract_dir}/MIGRATION_MANIFEST.txt"
echo ""
fi
# ------------------------------------------------------------------
# Clone repository
# ------------------------------------------------------------------
log_info "--- Cloning repository ---"
if [[ -d "$TARGET_DIR/.git" ]]; then
log_warn "Target directory already contains a git repo: ${TARGET_DIR}"
log_info "Skipping clone; will overlay migration files into existing repo."
elif [[ -d "$TARGET_DIR" ]]; then
# Directory exists but is not a git repo
log_warn "Target directory exists but is not a git repo: ${TARGET_DIR}"
log_info "Attempting clone into existing directory..."
git clone "$GITEA_REPO" "${TARGET_DIR}.tmp"
# Move .git and tracked files into existing directory
mv "${TARGET_DIR}.tmp/.git" "${TARGET_DIR}/"
# Checkout working tree into existing directory
(cd "$TARGET_DIR" && git checkout -- .)
rm -rf "${TARGET_DIR}.tmp"
log_ok "Cloned repository into existing directory."
else
git clone "$GITEA_REPO" "$TARGET_DIR"
log_ok "Cloned repository to: ${TARGET_DIR}"
fi
echo ""
# ------------------------------------------------------------------
# Overlay non-git files from migration archive
# ------------------------------------------------------------------
log_info "--- Restoring non-git files ---"
# Copy everything except claude-context (handled separately) and manifest
local item
for item in "$extract_dir"/*; do
local basename
basename="$(basename "$item")"
# Skip claude-context dir and manifest
if [[ "$basename" == "claude-context" || "$basename" == "MIGRATION_MANIFEST.txt" ]]; then
continue
fi
if [[ -d "$item" ]]; then
cp -a "$item" "$TARGET_DIR/"
log_ok "Restored directory: ${basename}"
elif [[ -f "$item" ]]; then
cp -a "$item" "$TARGET_DIR/"
log_ok "Restored file: ${basename}"
fi
done
# Handle dotfiles (hidden files/dirs from archive root)
for item in "$extract_dir"/.*; do
local basename
basename="$(basename "$item")"
[[ "$basename" == "." || "$basename" == ".." ]] && continue
if [[ -d "$item" ]]; then
# Merge directory contents (e.g., .claude/)
cp -a "$item"/. "$TARGET_DIR/${basename}/" 2>/dev/null || cp -a "$item" "$TARGET_DIR/"
log_ok "Restored directory: ${basename}"
elif [[ -f "$item" ]]; then
cp -a "$item" "$TARGET_DIR/"
log_ok "Restored file: ${basename}"
fi
done
echo ""
# ------------------------------------------------------------------
# Restore Claude AI context
# ------------------------------------------------------------------
log_info "--- Restoring Claude AI context ---"
local claude_ctx_src="${extract_dir}/claude-context"
if [[ -d "$claude_ctx_src" ]]; then
local project_name
project_name="$(derive_claude_project_name)"
local claude_ctx_dst="${HOME}/.claude/projects/${project_name}"
mkdir -p "$claude_ctx_dst"
cp -a "$claude_ctx_src"/. "$claude_ctx_dst/"
log_ok "Restored Claude context to: ${claude_ctx_dst}"
else
log_warn "No claude-context directory found in archive. Skipping."
fi
echo ""
# ------------------------------------------------------------------
# Initialize submodules
# ------------------------------------------------------------------
log_info "--- Initializing git submodules ---"
(cd "$TARGET_DIR" && git submodule update --init --recursive) && \
log_ok "Submodules initialized." || \
log_warn "Submodule initialization had issues. You may need to run it manually."
echo ""
# ------------------------------------------------------------------
# Summary and post-restore checklist
# ------------------------------------------------------------------
echo "============================================================"
echo " Restore Complete"
echo "============================================================"
echo ""
echo " Target: ${TARGET_DIR}"
echo " Warnings: ${WARN_COUNT}"
echo ""
echo "============================================================"
echo " Post-Restore Checklist"
echo "============================================================"
echo ""
echo " [ ] Verify credentials.md contains correct, unredacted values"
echo " File: ${TARGET_DIR}/credentials.md"
echo ""
echo " [ ] Set up Python virtual environment for MCP servers"
echo " cd ${TARGET_DIR} && python -m venv .venv"
echo " source .venv/bin/activate (or .venv\\Scripts\\activate on Windows)"
echo " pip install -r requirements.txt"
echo ""
echo " [ ] Configure Claude Code CLI"
echo " Run: claude (first launch will prompt for authentication)"
echo " Verify .claude/ context was restored correctly"
echo ""
echo " [ ] Test Gitea SSH access"
echo " Run: ssh -p 2222 git@172.16.3.20"
echo " If it fails, copy your SSH keys and update ~/.ssh/config"
echo ""
echo " [ ] Rebuild grepai index"
echo " The semantic search index is machine-specific."
echo " Run grepai indexing from within Claude Code."
echo ""
echo " [ ] Verify .env and .mcp.json values are correct for new machine"
echo ""
echo " [ ] Test database connectivity"
echo " Ensure 172.16.3.30:3306 is reachable from this machine"
echo ""
log_ok "Done."
}
main "$@"

5375
scripts/perms.json Normal file

File diff suppressed because it is too large Load Diff

639
scripts/sam.json Normal file
View File

@@ -0,0 +1,639 @@
{
"isFallbackPublicClient": true,
"signInAudience": "AzureADMultipleOrgs",
"displayName": "CIPP-SAM",
"web": {
"redirectUris": [
"https://login.microsoftonline.com/common/oauth2/nativeclient",
"https://localhost",
"http://localhost",
"http://localhost:8400"
]
},
"servicePrincipalLockConfiguration": {
"isEnabled": true,
"allProperties": true
},
"requiredResourceAccess": [
{
"resourceAppId": "c5393580-f805-4401-95e8-94b7a6ef2fc2",
"resourceAccess": [
{
"id": "594c1fb6-4f81-4475-ae41-0c394909246c",
"type": "Scope"
}
]
},
{
"resourceAppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85",
"resourceAccess": [
{
"id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9",
"type": "Role"
},
{
"id": "b0afded3-3588-46d8-8b3d-9842eff778da",
"type": "Role"
},
{
"id": "5e1e9171-754d-478c-812c-f1755a9a4c2d",
"type": "Role"
},
{
"id": "f3a65bd4-b703-46df-8f7e-0174fea562aa",
"type": "Role"
},
{
"id": "59a6b24b-4225-4393-8165-ebaec5f55d7a",
"type": "Role"
},
{
"id": "35930dcf-aceb-4bd1-b99a-8ffed403c974",
"type": "Role"
},
{
"id": "cac88765-0581-4025-9725-5ebc13f729ee",
"type": "Role"
},
{
"id": "1138cb37-bd11-4084-a2b7-9f71582aeddb",
"type": "Role"
},
{
"id": "78145de6-330d-4800-a6ce-494ff2d33d07",
"type": "Role"
},
{
"id": "9241abd9-d0e6-425a-bd4f-47ba86e767a4",
"type": "Role"
},
{
"id": "5b07b0dd-2377-4e44-a38d-703f09a0dc3c",
"type": "Role"
},
{
"id": "243333ab-4d21-40cb-a475-36241daa0842",
"type": "Role"
},
{
"id": "e330c4f0-4170-414e-a55a-2f022ec2b57b",
"type": "Role"
},
{
"id": "9255e99d-faf5-445e-bbf7-cb71482737c4",
"type": "Role"
},
{
"id": "8b9d79d0-ad75-4566-8619-f7500ecfcebe",
"type": "Scope"
},
{
"id": "5ac13192-7ace-4fcf-b828-1a26f28068ee",
"type": "Role"
},
{
"id": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
"type": "Role"
},
{
"id": "dbb9058a-0e50-45d7-ae91-66909b5d4664",
"type": "Role"
},
{
"id": "75359482-378d-4052-8f01-80520e7db3cd",
"type": "Role"
},
{
"id": "bf7b1a76-6e77-406b-b258-bf5c7720e98f",
"type": "Role"
},
{
"id": "62a82d76-70ea-41e2-9197-370581804d09",
"type": "Role"
},
{
"id": "dbaae8cf-10b5-4b86-a4a1-f871c94c6695",
"type": "Role"
},
{
"id": "19da66cb-0fb0-4390-b071-ebc76a349482",
"type": "Role"
},
{
"id": "6931bccd-447a-43d1-b442-00a195474933",
"type": "Role"
},
{
"id": "292d869f-3427-49a8-9dab-8c70152b74e9",
"type": "Role"
},
{
"id": "2cb92fee-97a3-4034-8702-24a6f5d0d1e9",
"type": "Role"
},
{
"id": "b6890674-9dd5-4e42-bb15-5af07f541ae1",
"type": "Role"
},
{
"id": "913b9306-0ce1-42b8-9137-6a7df690a760",
"type": "Role"
},
{
"id": "246dd0d5-5bd0-4def-940b-0421030a5b68",
"type": "Role"
},
{
"id": "be74164b-cff1-491c-8741-e671cb536e13",
"type": "Role"
},
{
"id": "25f85f3c-f66c-4205-8cd5-de92dd7f0cec",
"type": "Role"
},
{
"id": "29c18626-4985-4dcd-85c0-193eef327366",
"type": "Role"
},
{
"id": "01c0a623-fc9b-48e9-b794-0756f8e8f067",
"type": "Role"
},
{
"id": "999f8c63-0a38-4f1b-91fd-ed1947bdd1a9",
"type": "Role"
},
{
"id": "338163d7-f101-4c92-94ba-ca46fe52447c",
"type": "Role"
},
{
"id": "2f6817f8-7b12-4f0f-bc18-eeaf60705a9e",
"type": "Role"
},
{
"id": "230c1aed-a721-4c5d-9cb4-a90514e508ef",
"type": "Role"
},
{
"id": "2a60023f-3219-47ad-baa4-40e17cd02a1d",
"type": "Role"
},
{
"id": "025d3225-3f02-4882-b4c0-cd5b541a4e80",
"type": "Role"
},
{
"id": "04c55753-2244-4c25-87fc-704ab82a4f69",
"type": "Role"
},
{
"id": "bf394140-e372-4bf9-a898-299cfc7564e5",
"type": "Role"
},
{
"id": "34bf0e97-1971-4929-b999-9e2442d941d7",
"type": "Role"
},
{
"id": "19b94e34-907c-4f43-bde9-38b1909ed408",
"type": "Role"
},
{
"id": "a82116e5-55eb-4c41-a434-62fe8a61c773",
"type": "Role"
},
{
"id": "0121dc95-1b9f-4aed-8bac-58c5ac466691",
"type": "Role"
},
{
"id": "4437522e-9a86-4a41-a7da-e380edd4a97d",
"type": "Role"
},
{
"id": "741f803b-c850-494e-b5df-cde7c675a1ca",
"type": "Role"
},
{
"id": "50483e42-d915-4231-9639-7fdb7fd190e5",
"type": "Role"
},
{
"id": "bdfbf15f-ee85-4955-8675-146e8e5296b5",
"type": "Scope"
},
{
"id": "84bccea3-f856-4a8a-967b-dbe0a3d53a64",
"type": "Scope"
},
{
"id": "e4c9e354-4dc5-45b8-9e7c-e1393b0b1a20",
"type": "Scope"
},
{
"id": "b27a61ec-b99c-4d6a-b126-c4375d08ae30",
"type": "Scope"
},
{
"id": "101147cf-4178-4455-9d58-02b5c164e759",
"type": "Scope"
},
{
"id": "cc83893a-e232-4723-b5af-bd0b01bcfe65",
"type": "Scope"
},
{
"id": "9d8982ae-4365-4f57-95e9-d6032a4c0b87",
"type": "Scope"
},
{
"id": "2eadaff8-0bce-4198-a6b9-2cfc35a30075",
"type": "Scope"
},
{
"id": "0c3e411a-ce45-4cd1-8f30-f99a3efa7b11",
"type": "Scope"
},
{
"id": "2b61aa8a-6d36-4b2f-ac7b-f29867937c53",
"type": "Scope"
},
{
"id": "767156cb-16ae-4d10-8f8b-41b657c8c8c8",
"type": "Scope"
},
{
"id": "ebf0f66e-9fb1-49e4-a278-222f76911cf4",
"type": "Scope"
},
{
"id": "d649fb7c-72b4-4eec-b2b4-b15acf79e378",
"type": "Scope"
},
{
"id": "f3bfad56-966e-4590-a536-82ecf548ac1e",
"type": "Scope"
},
{
"id": "885f682f-a990-4bad-a642-36736a74b0c7",
"type": "Scope"
},
{
"id": "41ce6ca6-6826-4807-84f1-1c82854f7ee5",
"type": "Scope"
},
{
"id": "bac3b9c2-b516-4ef4-bd3b-c2ef73d8d804",
"type": "Scope"
},
{
"id": "11d4cd79-5ba5-460f-803f-e22c8ab85ccd",
"type": "Scope"
},
{
"id": "951183d1-1a61-466f-a6d1-1fde911bfd95",
"type": "Scope"
},
{
"id": "280b3b69-0437-44b1-bc20-3b2fca1ee3e9",
"type": "Scope"
},
{
"id": "7b3f05d5-f68c-4b8d-8c59-a2ecd12f24af",
"type": "Scope"
},
{
"id": "0883f392-0a7a-443d-8c76-16a6d39c7b63",
"type": "Scope"
},
{
"id": "3404d2bf-2b13-457e-a330-c24615765193",
"type": "Scope"
},
{
"id": "44642bfe-8385-4adc-8fc6-fe3cb2c375c3",
"type": "Scope"
},
{
"id": "0c5e8a55-87a6-4556-93ab-adc52c4d862d",
"type": "Scope"
},
{
"id": "662ed50a-ac44-4eef-ad86-62eed9be2a29",
"type": "Scope"
},
{
"id": "0e263e50-5827-48a4-b97c-d940288653c7",
"type": "Scope"
},
{
"id": "c5366453-9fb0-48a5-a156-24f0c49a4b84",
"type": "Scope"
},
{
"id": "2f9ee017-59c1-4f1d-9472-bd5529a7b311",
"type": "Scope"
},
{
"id": "4e46008b-f24c-477d-8fff-7bb4ec7aafe0",
"type": "Scope"
},
{
"id": "f81125ac-d3b7-4573-a3b2-7099cc39df9e",
"type": "Scope"
},
{
"id": "9e4862a5-b68f-479e-848a-4e07e25c9916",
"type": "Scope"
},
{
"id": "bb6f654c-d7fd-4ae3-85c3-fc380934f515",
"type": "Scope"
},
{
"id": "e0a7cdbb-08b0-4697-8264-0069786e9674",
"type": "Scope"
},
{
"id": "e383f46e-2787-4529-855e-0e479a3ffac0",
"type": "Scope"
},
{
"id": "a367ab51-6b49-43bf-a716-a1fb06d2a174",
"type": "Scope"
},
{
"id": "818c620a-27a9-40bd-a6a5-d96f7d610b4b",
"type": "Scope"
},
{
"id": "f6a3db3e-f7e8-4ed2-a414-557c8c9830be",
"type": "Scope"
},
{
"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
"type": "Scope"
},
{
"id": "37f7f235-527c-4136-accd-4a02d197296e",
"type": "Scope"
},
{
"id": "46ca0847-7e6b-426e-9775-ea810a948356",
"type": "Scope"
},
{
"id": "346c19ff-3fb2-4e81-87a0-bac9e33990c1",
"type": "Scope"
},
{
"id": "e67e6727-c080-415e-b521-e3f35d5248e9",
"type": "Scope"
},
{
"id": "4c06a06a-098a-4063-868e-5dfee3827264",
"type": "Scope"
},
{
"id": "572fea84-0151-49b2-9301-11cb16974376",
"type": "Scope"
},
{
"id": "b27add92-efb2-4f16-84f5-8108ba77985c",
"type": "Scope"
},
{
"id": "edb72de9-4252-4d03-a925-451deef99db7",
"type": "Scope"
},
{
"id": "7e823077-d88e-468f-a337-e18f1f0e6c7c",
"type": "Scope"
},
{
"id": "edd3c878-b384-41fd-95ad-e7407dd775be",
"type": "Scope"
},
{
"id": "ad902697-1014-4ef5-81ef-2b4301988e8c",
"type": "Scope"
},
{
"id": "4d135e65-66b8-41a8-9f8b-081452c91774",
"type": "Scope"
},
{
"id": "40b534c3-9552-4550-901b-23879c90bcf9",
"type": "Scope"
},
{
"id": "a8ead177-1889-4546-9387-f25e658e2a79",
"type": "Scope"
},
{
"id": "a84a9652-ffd3-496e-a991-22ba5529156a",
"type": "Scope"
},
{
"id": "14dad69e-099b-42c9-810b-d002981feec1",
"type": "Scope"
},
{
"id": "02e97553-ed7b-43d0-ab3c-f8bace0d040c",
"type": "Scope"
},
{
"id": "b955410e-7715-4a88-a940-dfd551018df3",
"type": "Scope"
},
{
"id": "d01b97e9-cbc0-49fe-810a-750afd5527a3",
"type": "Scope"
},
{
"id": "dc38509c-b87d-4da0-bd92-6bec988bac4a",
"type": "Scope"
},
{
"id": "6aedf524-7e1c-45a7-bd76-ded8cab8d0fc",
"type": "Scope"
},
{
"id": "128ca929-1a19-45e6-a3b8-435ec44a36ba",
"type": "Scope"
},
{
"id": "55896846-df78-47a7-aa94-8d3d4442ca7f",
"type": "Scope"
},
{
"id": "eda39fa6-f8cf-4c3c-a909-432c683e4c9b",
"type": "Scope"
},
{
"id": "aa07f155-3612-49b8-a147-6c590df35536",
"type": "Scope"
},
{
"id": "89fe6a52-be36-487e-b7d8-d061c450a026",
"type": "Scope"
},
{
"id": "7825d5d6-6049-4ce7-bdf6-3b8d53f4bcd0",
"type": "Scope"
},
{
"id": "485be79e-c497-4b35-9400-0e3fa7f2a5d4",
"type": "Scope"
},
{
"id": "4a06efd2-f825-4e34-813e-82a57b03d1ee",
"type": "Scope"
},
{
"id": "2104a4db-3a2f-4ea0-9dba-143d457dc666",
"type": "Scope"
},
{
"id": "0e755559-83fb-4b44-91d0-4cc721b9323e",
"type": "Scope"
},
{
"id": "39d65650-9d3e-4223-80db-a335590d027e",
"type": "Scope"
},
{
"id": "a9ff19c2-f369-4a95-9a25-ba9d460efc8e",
"type": "Scope"
},
{
"id": "b98bfd41-87c6-45cc-b104-e2de4f0dafb9",
"type": "Scope"
},
{
"id": "cac97e40-6730-457d-ad8d-4852fddab7ad",
"type": "Scope"
},
{
"id": "73e75199-7c3e-41bb-9357-167164dbb415",
"type": "Scope"
},
{
"id": "637d7bec-b31e-4deb-acc9-24275642a2c9",
"type": "Scope"
},
{
"id": "204e0828-b5ca-4ad8-b9f3-f32a958e7cc4",
"type": "Scope"
},
{
"id": "48971fc1-70d7-4245-af77-0beb29b53ee2",
"type": "Scope"
},
{
"id": "b7887744-6746-4312-813d-72daeaee7e2d",
"type": "Scope"
},
{
"id": "424b07a8-1209-4d17-9fe4-9018a93a1024",
"type": "Scope"
},
{
"id": "0a42382f-155c-4eb1-9bdc-21548ccaa387",
"type": "Role"
},
{
"id": "2d9bd318-b883-40be-9df7-63ec4fcdc424",
"type": "Role"
},
{
"id": "c8948c23-e66b-42db-83fd-770b71ab78d2",
"type": "Role"
},
{
"id": "a94a502d-0281-4d15-8cd2-682ac9362c4c",
"type": "Role"
}
]
},
{
"resourceAppId": "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd",
"resourceAccess": [
{
"id": "1cebfa2a-fb4d-419e-b5f9-839b4383e05a",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "dc50a0fb-09a3-484d-be87-e023b12c6440",
"type": "Role"
},
{
"id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99",
"type": "Role"
},
{
"id": "f9156939-25cd-4ba8-abfe-7fabcf003749",
"type": "Role"
},
{
"id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
"type": "Scope"
},
{
"id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415",
"type": "Scope"
},
{
"id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0",
"type": "Scope"
}
]
},
{
"resourceAppId": "48ac35b8-9aa8-4d74-927d-1f4a14a0b239",
"resourceAccess": [
{
"id": "e60370c1-e451-437e-aa6e-d76df38e5f15",
"type": "Scope"
}
]
},
{
"resourceAppId": "fc780465-2017-40d4-a0c5-307022471b92",
"resourceAccess": [
{
"id": "41269fc5-d04d-4bfd-bce7-43a51cea049a",
"type": "Role"
},
{
"id": "63a677ce-818c-4409-9d12-5c6d2e2a6bfe",
"type": "Scope"
}
]
}
]
}