Watch a short video overview of CAPWATCHSyncPWSH features, benefits, and architecture.
CAPWATCHSyncPWSH is a PowerShell-based automation toolkit for synchronizing CAP membership data from CAPWATCH with Microsoft Teams and Exchange Online. It leverages Microsoft Graph API and Azure Managed Identity to automate the creation, update, and management of Teams, users, and mail contacts based on authoritative CAPWATCH data.
This toolkit is designed to work with any CAP Wing and can be easily configured for your specific wing's requirements.
- Serverless Execution: Runs as an Azure Function App, so no dedicated server or VM is required.
- No Database Required: The solution does not use or require a database. All data processing is performed in-memory at runtime.
- Daily Data Refresh: CAPWATCH data is downloaded daily into Azure File Storage using a scheduled process.
- Real-Time Processing: When triggered, the Azure Function loads the latest CAPWATCH CSV files directly from Azure File Storage, processes them in real time, and runs queries against Microsoft 365 (Entra ID/Azure AD and Exchange Online) using Microsoft Graph API.
- Automation: All synchronization, creation, and update operations are performed automatically based on the latest data, with no manual intervention required.
-
Microsoft Teams Automation
- Creates and updates Teams for each unit.
- Synchronizes Team members and owners with CAPWATCH data.
- Ensures correct aliases and ownership for each Team.
-
Exchange Online Integration
- Manages mail contacts for members and guests.
- Removes or restores contacts based on membership status.
-
CAPWATCH Data Processing
- Reads and processes CAPWATCH CSV exports from Azure File Storage.
- Filters and normalizes member data for downstream automation.
-
Logging & Error Handling
- Logs all actions and errors to a dedicated logs directory.
- Stops execution if CAPWATCH data is stale.
-
Azure Integration
- Uses Azure Managed Identity for secure authentication.
- Follows Azure and Microsoft Graph best practices for permissions and security.
- Microsoft Graph PowerShell SDK installed and available in your environment.
- Azure Function App (or automation host) with Managed Identity enabled and granted the following Microsoft Graph API permissions (with admin consent):
Group.ReadWrite.AllTeamMember.ReadWrite.AllUser.Read.AllUser.ReadWrite.AllDirectory.ReadWrite.AllMail.Send(if using email notifications)
- CAPWATCH Data: Configure your Azure Function to read from Azure File Storage or place the latest CAPWATCH CSV files in the
$($env:HOME)/data/CAPWatchdirectory. - Wing Configuration: Set up environment variables for your specific wing (see Configuration Guide for details).
This toolkit is designed to be deployed as an Azure Function App. Follow these steps:
- Azure subscription with appropriate permissions
- PowerShell 7+ installed locally
- Azure CLI or Azure PowerShell module
- Git for cloning the repository
-
Clone the repository:
git clone https://github.com/ezaero/CAPWATCHpwsh.git cd CAPWATCHpwsh -
Deploy infrastructure:
# Use Terraform to deploy Azure resources cd terraform terraform init terraform plan terraform apply
-
Set up PowerShell modules:
# Upload required modules to Azure Storage Connect-AzAccount ./Download_Modules.ps1 ./Upload-ModulesToStorage.ps1 -StorageAccountName "your-storage-account" -ResourceGroup "your-rg"
-
Deploy function app:
# Deploy the PowerShell code func azure functionapp publish your-function-app-name --powershell
- DEPLOYMENT.md - Complete deployment guide with step-by-step instructions
- MODULE-SETUP.md - Quick reference for PowerShell module setup
- CONFIGURATION.md - Wing-specific configuration guide
- Module Management: Due to Azure Functions size limits, PowerShell modules are uploaded to Azure Storage and loaded at runtime
- Hybrid Loading: Uses both Azure Functions managed dependencies and custom storage-based loading for reliability
- Deployment Size: Optimized to ~53KB (down from 180MB+) through selective exclusions
- Run the main scripts in the appropriate subfolders (
updateTeams,checkAccounts, etc.) as needed. - Review and update configuration or environment variables as required for your deployment.
- All logs will be written to the
$($env:HOME)/logsdirectory.
This repository now includes a timer-triggered function updateTeams that synchronizes distribution lists (DLs) with Microsoft Teams membership. It's intended to ensure Teams match authoritative lists (distribution groups) maintained by your wing.
Key behavior
- The function can be driven by either a CSV mapping file next to the function (
group_to_team.csv) or a semicolon-separated environment variableGROUP_TEAM_PAIRS. - If a team display name begins with a CO prefix (for example
CO-022 Vance Brand Cadet) the helper will derive the DL addressCO-022@cowg.cap.govand attempt to resolve the DL by its email address first. If that fails it will fallback to searching by display name. - The function performs a dry-run by default (logs candidate members to add). Set
EXECUTE=truein Function App settings to actually add members. UseFORCE=trueto skip interactive confirmations. - The function assumes Microsoft Graph authentication is available in the Function runspace (managed identity or prior Connect-MgGraph call). Consider enabling a system-assigned managed identity and granting the function app appropriate Graph permissions.
Configuration and usage
- group_to_team.csv (preferred): place a CSV file next to
updateTeams/run.ps1with columnsGroupId,TeamDisplayName. The function will iterate mappings found in this file. - GROUP_TEAM_PAIRS (alternate): set an app setting like
GROUP_TEAM_PAIRS="<groupId1>:Team Name 1;<groupId2>:Team Name 2". - Environment variables:
EXECUTE(true/false) — whentruethe function will perform adds; otherwise it runs as dry-run (logs only).FORCE(true/false) — whentrueskip interactive prompts (recommended for non-interactive Function hosts).
Authentication notes
- For non-interactive execution, enable a system-assigned managed identity on the Function App and grant it the minimum Graph permissions needed (at least
Group.Read.AllandGroupMember.ReadWrite.AllorGroup.ReadWrite.All). The function expects a workingConnect-MgGraphsession in the runspace. You can either dot-source a shared auth helper that callsConnect-MgGraph -Identityat startup or let the function runtime call it directly.
Example
- Team display name:
CO-072 Boulder Composite-> derived DL:CO-072@cowg.cap.gov - If
CO-072@cowg.cap.govexists as a group'smailproperty in Graph the function will use that group to sync members into the team.
Operational guidance
- Start in dry-run mode (
EXECUTE=false) and review logs in$HOME/logsto confirm expected candidate membership. - When ready, enable
EXECUTE=truetemporarily (and optionallyFORCE=true) to perform adds. Monitor logs and audit membership in Teams.
/updateTeams– Synchronizes Microsoft Teams membership and ownership with CAPWATCH data for each unit./checkAccounts– Creates, updates, and restores user accounts and mail contacts in Azure AD and Exchange based on CAPWATCH data./Maintenance– Performs monthly cleanup: deletes expired member accounts and old log files./shared– Provides shared utility functions, including logging and Microsoft Graph helpers./DLAnnouncements– Manages distribution lists for CAP announcements, ensuring correct membership based on CAPWATCH data./DLOpsQuals– Automates distribution group membership for operational qualifications (e.g., pilots, aircrew, ES) using CAPWATCH and OpsQuals data./DLSeniorsCadets– Maintains distribution lists for senior and cadet members, updating group membership as CAPWATCH data changes./DLSpecTrack– Tracks and manages specialty distribution lists (e.g., specific qualifications or roles) for targeted communications./download-extract-capwatch– Handles downloading and extraction of CAPWATCH data files for use by other automation scripts./emailLogFile– Sends log files or notifications via email to administrators for audit and troubleshooting purposes.
- Do not commit secrets or credentials. Use environment variables or Azure Key Vault for sensitive data.
- Review all scripts for organization-specific information before making the repository public.
- Follow Azure best practices for automation and security.
- Use a
.gitignoreto exclude logs, output, credentials, and IDE files.
This project is licensed under the MIT License.
Contributions are welcome! Please open an issue or submit a pull request for improvements or bug fixes.
This project is provided as-is and is not officially supported by Civil Air Patrol or Microsoft. Use at your own risk.