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:
37
scripts/bgb-assign-exo-role.ps1
Normal file
37
scripts/bgb-assign-exo-role.ps1
Normal 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"
|
||||
81
scripts/bgb-check-lesley-ownership.ps1
Normal file
81
scripts/bgb-check-lesley-ownership.ps1
Normal 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
|
||||
11
scripts/bgb-find-leslie.ps1
Normal file
11
scripts/bgb-find-leslie.ps1
Normal 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
|
||||
102
scripts/bgb-lesley-disable-wipe.ps1
Normal file
102
scripts/bgb-lesley-disable-wipe.ps1
Normal 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
|
||||
33
scripts/bgb-lesley-exchange.ps1
Normal file
33
scripts/bgb-lesley-exchange.ps1
Normal 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"
|
||||
83
scripts/bgb-lesley-fix-rules.ps1
Normal file
83
scripts/bgb-lesley-fix-rules.ps1
Normal 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"
|
||||
62
scripts/bgb-lesley-mail-report-20260309.txt
Normal file
62
scripts/bgb-lesley-mail-report-20260309.txt
Normal 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
|
||||
150
scripts/bgb-lesley-mail-report.ps1
Normal file
150
scripts/bgb-lesley-mail-report.ps1
Normal 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"
|
||||
193
scripts/bgb-lesley-recover-review.ps1
Normal file
193
scripts/bgb-lesley-recover-review.ps1
Normal 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 ""
|
||||
71
scripts/bgb-lesley-verify-wipe.ps1
Normal file
71
scripts/bgb-lesley-verify-wipe.ps1
Normal 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"
|
||||
119
scripts/bgb-reenable-lesley.ps1
Normal file
119
scripts/bgb-reenable-lesley.ps1
Normal 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
|
||||
166
scripts/bgb-terminate-lesley.ps1
Normal file
166
scripts/bgb-terminate-lesley.ps1
Normal 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
|
||||
2
scripts/bgb-terminate-wrapper.cmd
Normal file
2
scripts/bgb-terminate-wrapper.cmd
Normal 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
|
||||
16
scripts/bgb-verify-users.ps1
Normal file
16
scripts/bgb-verify-users.ps1
Normal 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
|
||||
141
scripts/cipp-add-claude-app-template.ps1
Normal file
141
scripts/cipp-add-claude-app-template.ps1
Normal 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 ""
|
||||
640
scripts/claude-msp-combined-manifest.json
Normal file
640
scripts/claude-msp-combined-manifest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
188
scripts/claude-msp-onboard-tenant.ps1
Normal file
188
scripts/claude-msp-onboard-tenant.ps1
Normal 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."
|
||||
93
scripts/claude-msp-update-permissions.ps1
Normal file
93
scripts/claude-msp-update-permissions.ps1
Normal 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
|
||||
68
scripts/datto-smartbadge-check.ps1
Normal file
68
scripts/datto-smartbadge-check.ps1
Normal 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 $_ }
|
||||
100
scripts/datto-smartbadge-fix.reg
Normal file
100
scripts/datto-smartbadge-fix.reg
Normal 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
|
||||
27
scripts/df-check-desktop-creds.ps1
Normal file
27
scripts/df-check-desktop-creds.ps1
Normal 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
|
||||
62
scripts/df-check-jlohr-lockout.ps1
Normal file
62
scripts/df-check-jlohr-lockout.ps1
Normal 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
27
scripts/df-test-winrm.ps1
Normal 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
290
scripts/migration-pack.sh
Normal 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 "$@"
|
||||
296
scripts/migration-restore.sh
Normal file
296
scripts/migration-restore.sh
Normal 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
5375
scripts/perms.json
Normal file
File diff suppressed because it is too large
Load Diff
639
scripts/sam.json
Normal file
639
scripts/sam.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user