|
| 1 | +--- |
| 2 | +draft: false |
| 3 | +date: 2025-09-27 |
| 4 | +authors: |
| 5 | + - rfernandezdo |
| 6 | +categories: |
| 7 | + - Productividad / Microsoft 365 |
| 8 | +tags: |
| 9 | + - Teams |
| 10 | + - Shared Channels |
| 11 | + - PowerShell |
| 12 | + - Microsoft Graph |
| 13 | + - CSV |
| 14 | + - Bulk Operations |
| 15 | +--- |
| 16 | + |
| 17 | +# Añadir miembros a canales compartidos de Teams en modo bulk |
| 18 | + |
| 19 | +## Resumen |
| 20 | + |
| 21 | +Añadir usuarios (internos y externos) uno a uno en canales compartidos de Teams es tedioso. Este post muestra cómo automatizar el proceso usando PowerShell, Microsoft Graph y un archivo CSV como origen de datos para añadir múltiples usuarios de forma eficiente. |
| 22 | + |
| 23 | +<!-- more --> |
| 24 | + |
| 25 | +## ¿Qué son los canales compartidos en Teams? |
| 26 | + |
| 27 | +Los **canales compartidos** permiten colaborar con personas de tu organización y externas sin necesidad de cambiar de tenant. Puedes añadir: |
| 28 | + |
| 29 | +- **Usuarios internos** de tu propia organización |
| 30 | +- **Usuarios externos** que mantienen su identidad de origen mediante **B2B Direct Connect** |
| 31 | + |
| 32 | +A diferencia de los guest users tradicionales, los participantes externos en canales compartidos conservan su tenant original. |
| 33 | + |
| 34 | +**Características clave:** |
| 35 | + |
| 36 | +- Soportan usuarios internos y externos en el mismo canal |
| 37 | +- Usuarios externos no necesitan cuentas guest (B2B Collaboration) |
| 38 | +- Los usuarios externos conservan su tenant original |
| 39 | +- Configuración de B2B Direct Connect solo necesaria para usuarios externos |
| 40 | +- Usan UPN (User Principal Name) para identificación |
| 41 | + |
| 42 | +## Limitaciones importantes |
| 43 | + |
| 44 | +!!! warning "Restricciones críticas" |
| 45 | + - **Guest users NO pueden añadirse** a canales compartidos (incluso si su UserType es "Member") |
| 46 | + - Debes usar el **UPN del usuario**, no su email (si difieren en Microsoft Entra ID) |
| 47 | + - Requiere configuración previa de **B2B Direct Connect** entre organizaciones |
| 48 | + - Las políticas de **Information Barriers** pueden bloquear la adición |
| 49 | + |
| 50 | +## Arquitectura de la solución |
| 51 | + |
| 52 | +```mermaid |
| 53 | +flowchart LR |
| 54 | + A[CSV File] -->|Import-Csv| B[PowerShell Script] |
| 55 | + B -->|Connect-MgGraph| C[Microsoft Graph API] |
| 56 | + C -->|Add-MgTeamChannelMember| D[Shared Channel] |
| 57 | + D --> E[External Users] |
| 58 | +
|
| 59 | + style A fill:#e1f5ff |
| 60 | + style D fill:#c8e6c9 |
| 61 | + style E fill:#fff9c4 |
| 62 | +``` |
| 63 | + |
| 64 | +## Prerrequisitos |
| 65 | + |
| 66 | +**Configuración de tenant:** |
| 67 | + |
| 68 | +```powershell |
| 69 | +# Solo para usuarios externos: verificar B2B Direct Connect |
| 70 | +Connect-MgGraph -Scopes "Policy.Read.All" |
| 71 | +Get-MgPolicyCrossTenantAccessPolicyDefault |
| 72 | +``` |
| 73 | + |
| 74 | +**Módulos PowerShell necesarios:** |
| 75 | + |
| 76 | +```powershell |
| 77 | +# Instalar Microsoft.Graph (módulo principal) |
| 78 | +Install-Module -Name Microsoft.Graph -Scope CurrentUser |
| 79 | +
|
| 80 | +# Si solo necesitas Teams, puedes instalar el submódulo específico |
| 81 | +Install-Module Microsoft.Graph.Teams -Scope CurrentUser |
| 82 | +
|
| 83 | +# Verificar instalación |
| 84 | +Get-Module Microsoft.Graph* -ListAvailable |
| 85 | +``` |
| 86 | + |
| 87 | +**Permisos necesarios:** |
| 88 | + |
| 89 | +- `ChannelMember.ReadWrite.All` (application o delegated) |
| 90 | +- Permisos de propietario del canal compartido |
| 91 | + |
| 92 | +## Obtener usuarios desde un chat existente |
| 93 | + |
| 94 | +Si ya tienes los usuarios en un chat de Teams, puedes exportar la lista: |
| 95 | + |
| 96 | +### Opción 1: Desde Teams UI |
| 97 | + |
| 98 | +1. Abre el chat en Teams |
| 99 | +2. Haz clic en los participantes (icono de personas arriba) |
| 100 | +3. Copia manualmente los nombres y emails |
| 101 | + |
| 102 | +### Opción 2: PowerShell (más eficiente) |
| 103 | + |
| 104 | +```powershell |
| 105 | +# Instalar módulo si no lo tienes |
| 106 | +Install-Module -Name Microsoft.Graph -Scope CurrentUser |
| 107 | +
|
| 108 | +# Listar chats donde participas |
| 109 | +Connect-MgGraph -Scopes "Chat.Read" |
| 110 | +$Chats = Get-MgUserChat -UserId "[email protected]" -All |
| 111 | +
|
| 112 | +# Ver detalles de un chat específico (usa Topic para identificarlo) |
| 113 | +$Chats | Select-Object Id, Topic, ChatType | Format-Table |
| 114 | +
|
| 115 | +# Obtener miembros de un chat específico |
| 116 | +$ChatId = "TU_CHAT_ID" |
| 117 | +$ChatMembers = Get-MgChatMember -ChatId $ChatId |
| 118 | +
|
| 119 | +# Exportar a CSV |
| 120 | +$ChatMembers | ForEach-Object { |
| 121 | + $Member = Get-MgUser -UserId $_.UserId -ErrorAction SilentlyContinue |
| 122 | + if ($Member) { |
| 123 | + [PSCustomObject]@{ |
| 124 | + DisplayName = $Member.DisplayName |
| 125 | + UPN = $Member.UserPrincipalName |
| 126 | + Role = "" # Ajustar manualmente si necesitas owners |
| 127 | + } |
| 128 | + } |
| 129 | +} | Export-Csv -Path ".\members_from_chat.csv" -NoTypeInformation |
| 130 | +
|
| 131 | +Write-Host "CSV generado: members_from_chat.csv" |
| 132 | +``` |
| 133 | + |
| 134 | +## Formato del archivo CSV |
| 135 | + |
| 136 | +Crea un archivo `members.csv` con esta estructura (puedes generarlo desde el chat como se explicó arriba): |
| 137 | + |
| 138 | +```csv |
| 139 | +DisplayName,UPN,Role |
| 140 | + |
| 141 | +María García,[email protected],owner |
| 142 | + |
| 143 | + |
| 144 | +``` |
| 145 | + |
| 146 | +``` |
| 147 | +
|
| 148 | +**Campos:** |
| 149 | +
|
| 150 | +- `DisplayName`: Nombre completo del usuario |
| 151 | +- `UPN`: User Principal Name |
| 152 | + - **Usuarios internos**: `[email protected]` |
| 153 | + - **Usuarios externos**: `[email protected]` |
| 154 | +- `Role`: Vacío para miembros normales, `owner` para propietarios |
| 155 | +
|
| 156 | +!!! tip "UPN vs Email" |
| 157 | + Usa el UPN exacto de Entra ID. Si un usuario externo tiene email diferente al UPN, usa el UPN. |
| 158 | +
|
| 159 | +## Script PowerShell completo |
| 160 | +
|
| 161 | +```powershell |
| 162 | +# Variables de configuración |
| 163 | +$TeamId = "YOUR_TEAM_ID" |
| 164 | +$ChannelId = "YOUR_CHANNEL_ID" |
| 165 | +$CsvPath = ".\members.csv" |
| 166 | +
|
| 167 | +# Conectar a Microsoft Graph |
| 168 | +Connect-MgGraph -Scopes "ChannelMember.ReadWrite.All" |
| 169 | +
|
| 170 | +# Importar CSV |
| 171 | +$Members = Import-Csv -Path $CsvPath |
| 172 | +
|
| 173 | +# Contadores para reporte |
| 174 | +$SuccessCount = 0 |
| 175 | +$ErrorCount = 0 |
| 176 | +$ErrorLog = @() |
| 177 | +
|
| 178 | +# Procesar cada miembro |
| 179 | +foreach ($Member in $Members) { |
| 180 | + try { |
| 181 | + # Construir objeto de miembro |
| 182 | + $MemberParams = @{ |
| 183 | + "@odata.type" = "#microsoft.graph.aadUserConversationMember" |
| 184 | + "[email protected]" = "https://graph.microsoft.com/v1.0/users('$($Member.UPN)')" |
| 185 | + } |
| 186 | +
|
| 187 | + # Asignar rol si es owner |
| 188 | + if ($Member.Role -eq "owner") { |
| 189 | + $MemberParams["roles"] = @("owner") |
| 190 | + } |
| 191 | +
|
| 192 | + # Añadir miembro al canal |
| 193 | + New-MgTeamChannelMember -TeamId $TeamId ` |
| 194 | + -ChannelId $ChannelId ` |
| 195 | + -BodyParameter $MemberParams |
| 196 | +
|
| 197 | + Write-Host "✅ Añadido: $($Member.DisplayName) ($($Member.UPN))" -ForegroundColor Green |
| 198 | + $SuccessCount++ |
| 199 | +
|
| 200 | + } catch { |
| 201 | + $ErrorMessage = $_.Exception.Message |
| 202 | + Write-Host "❌ Error: $($Member.DisplayName) - $ErrorMessage" -ForegroundColor Red |
| 203 | + $ErrorCount++ |
| 204 | +
|
| 205 | + $ErrorLog += [PSCustomObject]@{ |
| 206 | + DisplayName = $Member.DisplayName |
| 207 | + UPN = $Member.UPN |
| 208 | + Error = $ErrorMessage |
| 209 | + } |
| 210 | + } |
| 211 | +} |
| 212 | +
|
| 213 | +# Resumen final |
| 214 | +Write-Host "`n=== RESUMEN ===" -ForegroundColor Cyan |
| 215 | +Write-Host "Total procesados: $($Members.Count)" |
| 216 | +Write-Host "Éxitos: $SuccessCount" -ForegroundColor Green |
| 217 | +Write-Host "Errores: $ErrorCount" -ForegroundColor Red |
| 218 | +
|
| 219 | +# Exportar log de errores si existen |
| 220 | +if ($ErrorLog.Count -gt 0) { |
| 221 | + $ErrorLog | Export-Csv -Path ".\errors_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" -NoTypeInformation |
| 222 | + Write-Host "`nLog de errores guardado en: errors_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" |
| 223 | +} |
| 224 | +
|
| 225 | +# Desconectar |
| 226 | +Disconnect-MgGraph |
| 227 | +``` |
| 228 | + |
| 229 | +## Obtener TeamId y ChannelId |
| 230 | + |
| 231 | +**Listar equipos:** |
| 232 | + |
| 233 | +```powershell |
| 234 | +Get-MgTeam | Select-Object DisplayName, Id |
| 235 | +``` |
| 236 | + |
| 237 | +**Listar canales de un equipo:** |
| 238 | + |
| 239 | +```powershell |
| 240 | +Get-MgTeamChannel -TeamId "TEAM_ID" | Select-Object DisplayName, Id, MembershipType |
| 241 | +``` |
| 242 | + |
| 243 | +!!! note "Identificar canales compartidos" |
| 244 | + Los canales compartidos tienen `MembershipType = "shared"` |
| 245 | + |
| 246 | +## Errores comunes y soluciones |
| 247 | + |
| 248 | +| Error | Causa | Solución | |
| 249 | +|-------|-------|----------| |
| 250 | +| `User not found` | UPN incorrecto o usuario no existe | Verificar UPN exacto en Entra ID (interno o externo) | |
| 251 | +| `Forbidden` | Falta B2B Direct Connect (solo externos) | Configurar cross-tenant access settings | |
| 252 | +| `Guest users cannot be added` | Intentas añadir un guest user | Solo B2B Direct Connect; los guests no son compatibles | |
| 253 | +| `Insufficient privileges` | Permisos insuficientes | Necesitas ser owner del canal + permisos Graph API | |
| 254 | +| `Already a member` | Usuario ya existe en el canal | Normal si reejecutas el script, puedes ignorar | |
| 255 | + |
| 256 | +## Verificar usuarios añadidos |
| 257 | + |
| 258 | +```powershell |
| 259 | +# Listar todos los miembros del canal compartido |
| 260 | +Get-MgTeamChannelMember -TeamId $TeamId -ChannelId $ChannelId | |
| 261 | + Select-Object DisplayName, Email, Roles, UserId |
| 262 | +``` |
| 263 | + |
| 264 | +## Alternativa: Usar Add-MgTeamChannelAllMember |
| 265 | + |
| 266 | +Para añadir múltiples usuarios en una **única llamada API** (más eficiente): |
| 267 | + |
| 268 | +```powershell |
| 269 | +# Construir array de miembros |
| 270 | +$AllMembers = @() |
| 271 | +foreach ($Member in $Members) { |
| 272 | + $MemberObject = @{ |
| 273 | + "@odata.type" = "#microsoft.graph.aadUserConversationMember" |
| 274 | + "[email protected]" = "https://graph.microsoft.com/v1.0/users('$($Member.UPN)')" |
| 275 | + "roles" = if ($Member.Role -eq "owner") { @("owner") } else { @() } |
| 276 | + } |
| 277 | + $AllMembers += $MemberObject |
| 278 | +} |
| 279 | +
|
| 280 | +# Añadir todos en una sola operación |
| 281 | +$BodyParams = @{ |
| 282 | + values = $AllMembers |
| 283 | +} |
| 284 | +
|
| 285 | +Invoke-MgGraphRequest -Method POST ` |
| 286 | + -Uri "https://graph.microsoft.com/v1.0/teams/$TeamId/channels/$ChannelId/members/add" ` |
| 287 | + -Body ($BodyParams | ConvertTo-Json -Depth 10) |
| 288 | +``` |
| 289 | + |
| 290 | +!!! tip "Cuándo usar cada método" |
| 291 | + - `New-MgTeamChannelMember`: Control granular + logging detallado por usuario |
| 292 | + - `Add-MgTeamChannelAllMember`: Máxima velocidad para grandes volúmenes (batching) |
| 293 | + |
| 294 | +## Buenas prácticas |
| 295 | + |
| 296 | +1. **Exportar desde chat existente**: Si ya tienes los usuarios en un chat, usa el script PowerShell para generar el CSV automáticamente |
| 297 | +2. **Validar UPNs antes de ejecutar**: Verifica que todos los UPNs existen (internos y externos) |
| 298 | +3. **Procesar en lotes pequeños**: Si tienes >100 usuarios, divide el CSV |
| 299 | +4. **Guardar logs de errores**: Usa el `-ErrorLog` del script para auditoría |
| 300 | +5. **B2B Direct Connect solo para externos**: Los usuarios internos no necesitan configuración adicional |
| 301 | +6. **No asumas UPN = Email**: Muchos tenants usan UPNs diferentes al email principal |
| 302 | + |
| 303 | +## Referencias |
| 304 | + |
| 305 | +- [Shared channels in Microsoft Teams](https://learn.microsoft.com/en-us/microsoftteams/shared-channels) |
| 306 | +- [Microsoft Graph - Add members to channel](https://learn.microsoft.com/en-us/graph/api/channel-post-members) |
| 307 | +- [Get chat members - Microsoft Graph](https://learn.microsoft.com/en-us/graph/api/chat-list-members) |
| 308 | +- [B2B Direct Connect documentation](https://learn.microsoft.com/en-us/entra/external-id/b2b-direct-connect-overview) |
| 309 | +- [Configure cross-tenant access settings](https://learn.microsoft.com/en-us/entra/external-id/cross-tenant-access-settings-b2b-direct-connect) |
0 commit comments