Files
claude-skills-reference/engineering-team/ms365-tenant-manager/scripts/powershell_generator.py
Alireza Rezvani b47f163b57 fix(skill): restructure ms365-tenant-manager for better PDA (#69) (#131)
- Add trigger phrases to frontmatter (M365 tenant, Office 365 admin, etc.)
- Add TOC to SKILL.md with proper section navigation
- Delete duplicate HOW_TO_USE.md (70% content overlap)
- Create references/ directory with deep-dive content:
  - powershell-templates.md: Ready-to-use script templates
  - security-policies.md: Conditional Access, MFA, DLP guide
  - troubleshooting.md: Common issues and solutions
- Move Python tools to scripts/ directory (standard pattern)
- Consolidate best practices into actionable workflows
- Fix second-person voice throughout

Addresses Progressive Disclosure Architecture feedback.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:59:10 +01:00

431 lines
14 KiB
Python

"""
PowerShell script generator for Microsoft 365 administration tasks.
Creates ready-to-use scripts with error handling and best practices.
"""
from typing import Dict, List, Any, Optional
class PowerShellScriptGenerator:
"""Generate PowerShell scripts for common Microsoft 365 admin tasks."""
def __init__(self, tenant_domain: str):
"""
Initialize generator with tenant domain.
Args:
tenant_domain: Primary domain of the Microsoft 365 tenant
"""
self.tenant_domain = tenant_domain
def generate_conditional_access_policy_script(self, policy_config: Dict[str, Any]) -> str:
"""
Generate script to create Conditional Access policy.
Args:
policy_config: Policy configuration parameters
Returns:
PowerShell script
"""
policy_name = policy_config.get('name', 'MFA Policy')
require_mfa = policy_config.get('require_mfa', True)
include_users = policy_config.get('include_users', 'All')
exclude_users = policy_config.get('exclude_users', [])
script = f"""<#
.SYNOPSIS
Create Conditional Access Policy: {policy_name}
.DESCRIPTION
Creates a Conditional Access policy with specified settings.
Policy will be created in report-only mode for testing.
#>
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess"
# Define policy parameters
$policyName = "{policy_name}"
# Create Conditional Access Policy
$conditions = @{{
Users = @{{
IncludeUsers = @("{include_users}")
"""
if exclude_users:
exclude_list = '", "'.join(exclude_users)
script += f""" ExcludeUsers = @("{exclude_list}")
"""
script += """ }
Applications = @{
IncludeApplications = @("All")
}
Locations = @{
IncludeLocations = @("All")
}
}
$grantControls = @{
"""
if require_mfa:
script += """ BuiltInControls = @("mfa")
Operator = "OR"
"""
script += """}
$policy = @{
DisplayName = $policyName
State = "enabledForReportingButNotEnforced" # Start in report-only mode
Conditions = $conditions
GrantControls = $grantControls
}
try {
$newPolicy = New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
Write-Host "✓ Conditional Access policy created: $($newPolicy.DisplayName)" -ForegroundColor Green
Write-Host " Policy ID: $($newPolicy.Id)" -ForegroundColor Cyan
Write-Host " State: Report-only (test before enforcing)" -ForegroundColor Yellow
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host "1. Review policy in Azure AD > Security > Conditional Access"
Write-Host "2. Monitor sign-in logs for impact assessment"
Write-Host "3. When ready, change state to 'enabled' to enforce"
} catch {
Write-Host "✗ Error creating policy: $_" -ForegroundColor Red
}
Disconnect-MgGraph
"""
return script
def generate_security_audit_script(self) -> str:
"""
Generate comprehensive security audit script.
Returns:
PowerShell script for security assessment
"""
script = """<#
.SYNOPSIS
Microsoft 365 Security Audit Report
.DESCRIPTION
Performs comprehensive security audit and generates detailed report.
Checks: MFA status, admin accounts, inactive users, permissions, licenses
.OUTPUTS
CSV reports with security findings
#>
# Connect to services
Connect-MgGraph -Scopes "Directory.Read.All", "User.Read.All", "AuditLog.Read.All"
Connect-ExchangeOnline
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$reportPath = "SecurityAudit_$timestamp"
New-Item -ItemType Directory -Path $reportPath -Force | Out-Null
Write-Host "Starting Security Audit..." -ForegroundColor Cyan
Write-Host ""
# 1. Check MFA Status
Write-Host "[1/7] Checking MFA status for all users..." -ForegroundColor Yellow
$mfaReport = @()
$users = Get-MgUser -All -Property Id,DisplayName,UserPrincipalName,AccountEnabled
foreach ($user in $users) {
$authMethods = Get-MgUserAuthenticationMethod -UserId $user.Id
$hasMFA = $authMethods.Count -gt 1 # More than just password
$mfaReport += [PSCustomObject]@{
UserPrincipalName = $user.UserPrincipalName
DisplayName = $user.DisplayName
AccountEnabled = $user.AccountEnabled
MFAEnabled = $hasMFA
AuthMethodsCount = $authMethods.Count
}
}
$mfaReport | Export-Csv -Path "$reportPath/MFA_Status.csv" -NoTypeInformation
$usersWithoutMFA = ($mfaReport | Where-Object { $_.MFAEnabled -eq $false -and $_.AccountEnabled -eq $true }).Count
Write-Host " Users without MFA: $usersWithoutMFA" -ForegroundColor $(if($usersWithoutMFA -gt 0){'Red'}else{'Green'})
# 2. Check Admin Accounts
Write-Host "[2/7] Auditing admin role assignments..." -ForegroundColor Yellow
$adminRoles = Get-MgDirectoryRole -All
$adminReport = @()
foreach ($role in $adminRoles) {
$members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id
foreach ($member in $members) {
$user = Get-MgUser -UserId $member.Id -ErrorAction SilentlyContinue
if ($user) {
$adminReport += [PSCustomObject]@{
UserPrincipalName = $user.UserPrincipalName
DisplayName = $user.DisplayName
Role = $role.DisplayName
AccountEnabled = $user.AccountEnabled
}
}
}
}
$adminReport | Export-Csv -Path "$reportPath/Admin_Roles.csv" -NoTypeInformation
Write-Host " Total admin assignments: $($adminReport.Count)" -ForegroundColor Cyan
# 3. Check Inactive Users
Write-Host "[3/7] Identifying inactive users (90+ days)..." -ForegroundColor Yellow
$inactiveDate = (Get-Date).AddDays(-90)
$inactiveUsers = @()
foreach ($user in $users) {
$signIns = Get-MgAuditLogSignIn -Filter "userId eq '$($user.Id)'" -Top 1
$lastSignIn = if ($signIns) { $signIns[0].CreatedDateTime } else { $null }
if ($lastSignIn -and $lastSignIn -lt $inactiveDate -and $user.AccountEnabled) {
$inactiveUsers += [PSCustomObject]@{
UserPrincipalName = $user.UserPrincipalName
DisplayName = $user.DisplayName
LastSignIn = $lastSignIn
DaysSinceSignIn = ((Get-Date) - $lastSignIn).Days
}
}
}
$inactiveUsers | Export-Csv -Path "$reportPath/Inactive_Users.csv" -NoTypeInformation
Write-Host " Inactive users found: $($inactiveUsers.Count)" -ForegroundColor $(if($inactiveUsers.Count -gt 0){'Yellow'}else{'Green'})
# 4. Check Guest Users
Write-Host "[4/7] Reviewing guest user access..." -ForegroundColor Yellow
$guestUsers = Get-MgUser -Filter "userType eq 'Guest'" -All
$guestReport = $guestUsers | Select-Object UserPrincipalName, DisplayName, AccountEnabled, CreatedDateTime
$guestReport | Export-Csv -Path "$reportPath/Guest_Users.csv" -NoTypeInformation
Write-Host " Guest users: $($guestUsers.Count)" -ForegroundColor Cyan
# 5. Check License Usage
Write-Host "[5/7] Analyzing license allocation..." -ForegroundColor Yellow
$licenses = Get-MgSubscribedSku
$licenseReport = @()
foreach ($license in $licenses) {
$licenseReport += [PSCustomObject]@{
ProductName = $license.SkuPartNumber
TotalLicenses = $license.PrepaidUnits.Enabled
AssignedLicenses = $license.ConsumedUnits
AvailableLicenses = $license.PrepaidUnits.Enabled - $license.ConsumedUnits
UtilizationPercent = [math]::Round(($license.ConsumedUnits / $license.PrepaidUnits.Enabled) * 100, 2)
}
}
$licenseReport | Export-Csv -Path "$reportPath/License_Usage.csv" -NoTypeInformation
Write-Host " License SKUs analyzed: $($licenses.Count)" -ForegroundColor Cyan
# 6. Check Mailbox Permissions
Write-Host "[6/7] Auditing mailbox delegations..." -ForegroundColor Yellow
$mailboxes = Get-Mailbox -ResultSize Unlimited
$delegationReport = @()
foreach ($mailbox in $mailboxes) {
$permissions = Get-MailboxPermission -Identity $mailbox.Identity |
Where-Object { $_.User -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false }
foreach ($perm in $permissions) {
$delegationReport += [PSCustomObject]@{
Mailbox = $mailbox.UserPrincipalName
DelegatedTo = $perm.User
AccessRights = $perm.AccessRights -join ", "
}
}
}
$delegationReport | Export-Csv -Path "$reportPath/Mailbox_Delegations.csv" -NoTypeInformation
Write-Host " Delegated mailboxes: $($delegationReport.Count)" -ForegroundColor Cyan
# 7. Check Conditional Access Policies
Write-Host "[7/7] Reviewing Conditional Access policies..." -ForegroundColor Yellow
$caPolicies = Get-MgIdentityConditionalAccessPolicy
$caReport = $caPolicies | Select-Object DisplayName, State, CreatedDateTime,
@{N='IncludeUsers';E={$_.Conditions.Users.IncludeUsers -join '; '}},
@{N='RequiresMFA';E={$_.GrantControls.BuiltInControls -contains 'mfa'}}
$caReport | Export-Csv -Path "$reportPath/ConditionalAccess_Policies.csv" -NoTypeInformation
Write-Host " Conditional Access policies: $($caPolicies.Count)" -ForegroundColor Cyan
# Generate Summary Report
Write-Host ""
Write-Host "=== Security Audit Summary ===" -ForegroundColor Green
Write-Host ""
Write-Host "Users:" -ForegroundColor Cyan
Write-Host " Total Users: $($users.Count)"
Write-Host " Users without MFA: $usersWithoutMFA $(if($usersWithoutMFA -gt 0){'⚠️'}else{''})"
Write-Host " Inactive Users (90+ days): $($inactiveUsers.Count) $(if($inactiveUsers.Count -gt 0){'⚠️'}else{''})"
Write-Host " Guest Users: $($guestUsers.Count)"
Write-Host ""
Write-Host "Administration:" -ForegroundColor Cyan
Write-Host " Admin Role Assignments: $($adminReport.Count)"
Write-Host " Conditional Access Policies: $($caPolicies.Count)"
Write-Host ""
Write-Host "Licenses:" -ForegroundColor Cyan
foreach ($lic in $licenseReport) {
Write-Host " $($lic.ProductName): $($lic.AssignedLicenses)/$($lic.TotalLicenses) ($($lic.UtilizationPercent)%)"
}
Write-Host ""
Write-Host "Reports saved to: $reportPath" -ForegroundColor Green
Write-Host ""
Write-Host "Recommended Actions:" -ForegroundColor Yellow
if ($usersWithoutMFA -gt 0) {
Write-Host " 1. Enable MFA for users without MFA"
}
if ($inactiveUsers.Count -gt 0) {
Write-Host " 2. Review and disable inactive user accounts"
}
if ($guestUsers.Count -gt 10) {
Write-Host " 3. Review guest user access and remove unnecessary guests"
}
# Disconnect
Disconnect-MgGraph
Disconnect-ExchangeOnline -Confirm:$false
"""
return script
def generate_bulk_license_assignment_script(self, users_csv_path: str, license_sku: str) -> str:
"""
Generate script for bulk license assignment from CSV.
Args:
users_csv_path: Path to CSV with user emails
license_sku: License SKU to assign
Returns:
PowerShell script
"""
script = f"""<#
.SYNOPSIS
Bulk License Assignment from CSV
.DESCRIPTION
Assigns {license_sku} license to users listed in CSV file.
CSV must have 'UserPrincipalName' column.
.PARAMETER CsvPath
Path to CSV file with user list
#>
param(
[Parameter(Mandatory=$true)]
[string]$CsvPath = "{users_csv_path}"
)
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All"
# Get license SKU ID
$targetSku = "{license_sku}"
$licenseSkuId = (Get-MgSubscribedSku -All | Where-Object {{$_.SkuPartNumber -eq $targetSku}}).SkuId
if (-not $licenseSkuId) {{
Write-Host "✗ License SKU not found: $targetSku" -ForegroundColor Red
exit
}}
Write-Host "License SKU found: $targetSku" -ForegroundColor Green
Write-Host "SKU ID: $licenseSkuId" -ForegroundColor Cyan
Write-Host ""
# Import users from CSV
$users = Import-Csv -Path $CsvPath
if (-not $users) {{
Write-Host "✗ No users found in CSV file" -ForegroundColor Red
exit
}}
Write-Host "Found $($users.Count) users in CSV" -ForegroundColor Cyan
Write-Host ""
# Process each user
$successCount = 0
$errorCount = 0
$results = @()
foreach ($user in $users) {{
$userEmail = $user.UserPrincipalName
try {{
# Get user
$mgUser = Get-MgUser -UserId $userEmail -ErrorAction Stop
# Check if user already has license
$currentLicenses = Get-MgUserLicenseDetail -UserId $mgUser.Id
if ($currentLicenses.SkuId -contains $licenseSkuId) {{
Write-Host " ⊘ $userEmail - Already has license" -ForegroundColor Yellow
$results += [PSCustomObject]@{{
UserPrincipalName = $userEmail
Status = "Skipped"
Message = "Already licensed"
}}
continue
}}
# Assign license
$licenseParams = @{{
AddLicenses = @(
@{{
SkuId = $licenseSkuId
}}
)
}}
Set-MgUserLicense -UserId $mgUser.Id -BodyParameter $licenseParams
Write-Host " ✓ $userEmail - License assigned successfully" -ForegroundColor Green
$successCount++
$results += [PSCustomObject]@{{
UserPrincipalName = $userEmail
Status = "Success"
Message = "License assigned"
}}
}} catch {{
Write-Host " ✗ $userEmail - Error: $_" -ForegroundColor Red
$errorCount++
$results += [PSCustomObject]@{{
UserPrincipalName = $userEmail
Status = "Failed"
Message = $_.Exception.Message
}}
}}
}}
# Export results
$resultsPath = "LicenseAssignment_Results_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$results | Export-Csv -Path $resultsPath -NoTypeInformation
# Summary
Write-Host ""
Write-Host "=== Summary ===" -ForegroundColor Cyan
Write-Host "Total users processed: $($users.Count)"
Write-Host "Successfully assigned: $successCount" -ForegroundColor Green
Write-Host "Errors: $errorCount" -ForegroundColor $(if($errorCount -gt 0){{'Red'}}else{{'Green'}})
Write-Host ""
Write-Host "Results saved to: $resultsPath" -ForegroundColor Cyan
# Disconnect
Disconnect-MgGraph
"""
return script