Bạn cần chuẩn bị gì?
- Máy tính Windows 10 hoặc Windows 11 có Internet.
- Tài khoản Microsoft của công ty/trường học có quyền thêm thành viên.
- Danh sách email cần thêm, lưu trong Excel hoặc file CSV.
- Liên kết của đúng Team, trong liên kết phải có chữ
groupId=.
Tạo một thư mục riêng
Giữ mọi file ở cùng một chỗ để tránh nhập sai đường dẫn.
- Mở File Explorer (biểu tượng thư mục màu vàng).
- Mở thư mục Documents hoặc Downloads.
- Nhấp chuột phải vào vùng trống → New → Folder.
- Đặt tên thư mục là
TeamsMembers, rồi mở thư mục đó.
Tạo file teams.ps1
Cách dễ nhất là dùng nút tải. Bạn cũng có thể sao chép bằng Notepad.
Cách A — dễ nhất
- Nhấn nút Tải file teams.ps1 bên dưới.
- Trình duyệt sẽ tải một file tên
teams.ps1. - Chuyển file vừa tải vào thư mục
TeamsMembersđã tạo ở bước 1.
Cách B — dùng nút sao chép
- Đi tới phần Toàn bộ mã nguồn ở cuối trang.
- Nhấn Sao chép toàn bộ.
- Mở Notepad, dán code bằng
Ctrl + V. - Chọn File → Save As. Ở ô tên file, nhập chính xác
teams.ps1. - Ở Save as type, chọn All files (*.*); ở Encoding, chọn UTF-8; rồi lưu vào thư mục
TeamsMembers.
teams.ps1.txt, hãy đổi tên thành teams.ps1. Windows có thể hỏi xác nhận; chọn Yes.Cài module MicrosoftTeams
Đây là công cụ chính thức để PowerShell đăng nhập và quản lý Microsoft Teams.
Script có thể tự cài module này trong lần chạy đầu tiên. Để kiểm tra trước và tránh phải chờ lúc chạy, bạn nên cài theo các bước sau:
- Nhấn nút Start của Windows.
- Gõ PowerShell, sau đó mở Windows PowerShell. Không bắt buộc mở bằng quyền Administrator.
- Sao chép lệnh dưới đây, dán vào PowerShell và nhấn Enter.
Install-Module -Name MicrosoftTeams -Scope CurrentUser -Force -AllowClobber
- Nếu PowerShell hỏi cài NuGet provider, nhập
Yrồi nhấn Enter. - Nếu hỏi có tin tưởng PSGallery hay không, nhập
YhoặcA, rồi nhấn Enter. - Đợi đến khi PowerShell hiện lại dòng nhập lệnh. Việc cài đặt có thể mất vài phút.
- Dùng lệnh bên dưới để kiểm tra:
Get-Module -ListAvailable MicrosoftTeams
Nếu kết quả có dòng tên MicrosoftTeams và số phiên bản, module đã được cài thành công. Tham khảo tài liệu Connect-MicrosoftTeams chính thức khi cần.
Chuẩn bị danh sách CSV
Dòng đầu phải có đúng chữ Email.
Nếu dùng Excel
- Mở Excel và tạo bảng mới.
- Ô A1 nhập
Email. - Từ ô A2 trở xuống, mỗi dòng nhập một địa chỉ email.
- Chọn File → Save As.
- Đặt tên
student.csv; loại file chọn CSV UTF-8 (Comma delimited) (*.csv). - Lưu vào cùng thư mục
TeamsMembers. Nếu Excel cảnh báo định dạng, chọn tiếp tục lưu CSV.
Email
student1@example.com
student2@example.com
Email.Lấy liên kết của Team
Không dùng liên kết của cuộc họp.
- Mở Microsoft Teams và vào đúng Team muốn thêm người.
- Nhấn dấu ba chấm … cạnh tên Team.
- Chọn mục lấy/sao chép liên kết của Team. Tên mục có thể là Get link to team hoặc Copy link.
- Dán tạm vào Notepad và kiểm tra liên kết có đoạn
groupId=.
Chạy chương trình
Làm đúng thứ tự dưới đây, đặc biệt là mở PowerShell trong đúng thư mục.
- Mở thư mục
TeamsMemberstrong File Explorer. - Nhấp vào thanh địa chỉ ở phía trên cửa sổ (nơi hiện đường dẫn thư mục).
- Xóa nội dung trên thanh địa chỉ, gõ
powershell, rồi nhấn Enter. Một cửa sổ PowerShell sẽ mở đúng tại thư mục này. - Sao chép lệnh sau, dán vào PowerShell và nhấn Enter.
powershell -NoProfile -ExecutionPolicy Bypass -File .\teams.ps1
- Khi thấy Please enter the Microsoft Teams link, dán liên kết Team bằng cách nhấp chuột phải hoặc nhấn
Ctrl + V, rồi nhấn Enter. - Khi thấy Please enter the CSV file path, nhập
.\student.csvrồi nhấn Enter. - Khi thấy dòng Opening the Microsoft Teams sign-in window..., một cửa sổ đăng nhập Microsoft sẽ mở ra.
- Chọn tài khoản công ty/trường học có quyền thêm thành viên vào Team, nhập mật khẩu và hoàn tất xác thực nhiều bước nếu được yêu cầu.
- Sau khi đăng nhập thành công, quay lại PowerShell và chờ. Không đóng cửa sổ trong lúc chương trình đang chạy.
Xem kết quả
Cuối quá trình, chương trình báo số lượng và tạo file nhật ký.
| Thông báo | Ý nghĩa |
|---|---|
| Added | Số người đã được thêm thành công. |
| Skipped | Dòng trống hoặc người đã có trong Team; không cần thêm lại. |
| Failed | Không thêm được; cần xem thông báo lỗi. |
Mở thư mục con logs nằm trong thư mục TeamsMembers:
teams-results-....csv: toàn bộ kết quả.teams-failed-....csv: chỉ những email bị lỗi.
Có thể mở hai file này bằng Excel. Nếu Failed: 0, quá trình đã hoàn tất tốt.
Xử lý lỗi thường gặp
Không cài được module MicrosoftTeams
Kiểm tra kết nối Internet rồi mở PowerShell và chạy lại lệnh ở bước 3. Nếu máy công ty chặn PSGallery, hãy gửi thông báo lỗi cho bộ phận IT để họ cài module MicrosoftTeams cho tài khoản Windows của bạn.
Cửa sổ đăng nhập không xuất hiện hoặc tự đóng
Đóng PowerShell, mở lại rồi chạy chương trình một lần nữa. Nếu vẫn lỗi, chạy riêng lệnh Connect-MicrosoftTeams và gửi nguyên thông báo lỗi cho bộ phận IT; chính sách đăng nhập của tổ chức có thể đang chặn tài khoản hoặc thiết bị.
“The CSV file does not exist”
Đảm bảo file có tên chính xác student.csv, nằm cùng thư mục với teams.ps1, và bạn đã nhập .\student.csv.
“The CSV file must contain an Email column”
Mở file CSV bằng Excel. Ô A1 phải là chính xác Email, không thêm dấu cách hoặc từ khác.
“Invalid Microsoft Teams link”
Bạn có thể đã dán liên kết cuộc họp hoặc kênh. Sao chép lại liên kết của Team và kiểm tra có đoạn groupId=.
“Need admin approval”, “Forbidden” hoặc HTTP 403
Tài khoản chưa có quyền cần thiết. Hãy đăng nhập bằng chủ sở hữu Team, Teams Administrator hoặc Global Administrator; nếu vẫn lỗi, gửi ảnh lỗi cho quản trị viên Microsoft 365.
HTTP 404 hoặc “Failed to find user”
Kiểm tra lại chính tả email. Người đó cũng phải tồn tại trong cùng hệ thống Microsoft 365 của công ty/trường học.
Người dùng đã có trong Team
Chương trình ghi trạng thái skipped. Đây không phải lỗi và không cần xử lý.
Toàn bộ code teams.ps1
Code bên dưới được nhúng trực tiếp trong trang. Nhấn nút để sao chép tất cả, sau đó dán vào Notepad nếu bạn không dùng nút tải file.
[CmdletBinding()]
param (
[Alias('l', 'Link')]
[string]$TeamLink,
[Alias('c', 'Csv')]
[string]$CsvPath,
[Alias('o', 'Output')]
[string]$OutputPath,
[Alias('f', 'FailedOutput')]
[string]$FailedOutputPath,
[Alias('h')]
[switch]$Help
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Show-Usage {
@'
Usage:
.\teams.ps1 -TeamLink "MICROSOFT_TEAMS_LINK" -CsvPath ".\student.csv"
.\teams.ps1 -Link "MICROSOFT_TEAMS_LINK" -Csv ".\student.csv" -Output ".\results.csv" -FailedOutput ".\failed.csv"
Options:
-TeamLink, -Link, -l Microsoft Teams link that contains groupId=...
-CsvPath, -Csv, -c CSV file path. The file must contain an Email column.
-OutputPath, -Output, -o Results CSV path. Default: logs\teams-results-YYYYMMDD-HHMMSS.csv
-FailedOutputPath, Failed-only CSV path. Default: logs\teams-failed-YYYYMMDD-HHMMSS.csv
-FailedOutput, -f
-Help, -h Show this help message.
'@
}
function Get-QueryParameter {
param (
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Name
)
$pattern = '(?:[?&])' + [regex]::Escape($Name) + '=([^&]*)'
$match = [regex]::Match($Uri, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
if (-not $match.Success) {
return $null
}
return [System.Uri]::UnescapeDataString($match.Groups[1].Value.Replace('+', ' '))
}
function ConvertTo-CsvField {
param ([AllowNull()][object]$Value)
$text = if ($null -eq $Value) { '' } else { [string]$Value }
$text = $text.Replace('"', '""').Replace("`r", ' ').Replace("`n", ' ')
return '"' + $text + '"'
}
function Add-CsvLine {
param (
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]]$Values
)
$line = (($Values | ForEach-Object { ConvertTo-CsvField $_ }) -join ',') + [Environment]::NewLine
[System.IO.File]::AppendAllText($Path, $line, (New-Object System.Text.UTF8Encoding($false)))
}
function Initialize-OutputFile {
param (
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string[]]$Header
)
$fullPath = [System.IO.Path]::GetFullPath($Path)
$directory = [System.IO.Path]::GetDirectoryName($fullPath)
if (-not [string]::IsNullOrWhiteSpace($directory)) {
$null = New-Item -ItemType Directory -Path $directory -Force
}
$headerLine = (($Header | ForEach-Object { ConvertTo-CsvField $_ }) -join ',') + [Environment]::NewLine
[System.IO.File]::WriteAllText($fullPath, $headerLine, (New-Object System.Text.UTF8Encoding($false)))
return $fullPath
}
function Write-Result {
param (
[AllowEmptyString()][string]$Email,
[Parameter(Mandatory = $true)][string]$Status,
[AllowEmptyString()][string]$Message
)
Add-CsvLine -Path $script:OutputPath -Values @($Email, $Status, $Message, '')
if ($Status -eq 'failed') {
Add-CsvLine -Path $script:FailedOutputPath -Values @($Email, $Message, '')
}
}
function Install-MicrosoftTeamsIfNeeded {
if (Get-Module -ListAvailable -Name MicrosoftTeams) {
return
}
Write-Host 'MicrosoftTeams module is not installed. Installing it for the current Windows user...'
if ($PSVersionTable.PSVersion.Major -lt 6) {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
}
if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
Write-Host 'Installing the NuGet package provider...'
$null = Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force
}
Install-Module -Name MicrosoftTeams -Scope CurrentUser -Force -AllowClobber
}
function Connect-ToMicrosoftTeams {
param ([AllowEmptyString()][string]$TenantId)
Install-MicrosoftTeamsIfNeeded
Import-Module MicrosoftTeams -ErrorAction Stop
Write-Host 'Opening the Microsoft Teams sign-in window...'
if ([string]::IsNullOrWhiteSpace($TenantId)) {
$null = Connect-MicrosoftTeams
}
else {
$null = Connect-MicrosoftTeams -TenantId $TenantId
}
}
function Get-ExistingTeamMembers {
param ([Parameter(Mandatory = $true)][string]$TeamId)
$existing = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($teamUser in @(Get-TeamUser -GroupId $TeamId)) {
$upn = [string]$teamUser.User
if (-not [string]::IsNullOrWhiteSpace($upn)) {
$null = $existing.Add($upn.Trim())
}
}
# Prevent PowerShell from expanding the HashSet into a normal array.
return ,$existing
}
if ($Help) {
Show-Usage
exit 0
}
$connected = $false
$exitCode = 0
try {
if ([string]::IsNullOrWhiteSpace($TeamLink)) {
$TeamLink = Read-Host 'Please enter the Microsoft Teams link'
}
if ([string]::IsNullOrWhiteSpace($CsvPath)) {
$CsvPath = Read-Host 'Please enter the CSV file path'
}
if (-not (Test-Path -LiteralPath $CsvPath -PathType Leaf)) {
throw "The CSV file does not exist: $CsvPath"
}
$teamId = Get-QueryParameter -Uri $TeamLink -Name 'groupId'
if ([string]::IsNullOrWhiteSpace($teamId)) {
throw 'Invalid Microsoft Teams link. It must contain the groupId parameter.'
}
$tenantId = Get-QueryParameter -Uri $TeamLink -Name 'tenantId'
Write-Host "Extracted Team ID: $teamId"
if ([string]::IsNullOrWhiteSpace($tenantId)) {
Write-Host 'No tenantId found in the Teams link. MicrosoftTeams will use the account tenant.'
$tenantId = ''
}
else {
Write-Host "Extracted Tenant ID: $tenantId"
}
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
$OutputPath = Join-Path 'logs' "teams-results-$timestamp.csv"
}
if ([string]::IsNullOrWhiteSpace($FailedOutputPath)) {
$FailedOutputPath = Join-Path 'logs' "teams-failed-$timestamp.csv"
}
$OutputPath = Initialize-OutputFile -Path $OutputPath -Header @('Email', 'Status', 'Message', 'HTTPStatus')
$FailedOutputPath = Initialize-OutputFile -Path $FailedOutputPath -Header @('Email', 'Message', 'HTTPStatus')
$headerLine = [System.IO.File]::ReadLines([System.IO.Path]::GetFullPath($CsvPath)) | Select-Object -First 1
if ([string]::IsNullOrWhiteSpace($headerLine)) {
throw 'The CSV file is empty.'
}
$headerLine = $headerLine.TrimStart([char]0xFEFF)
$headerProbe = @(ConvertFrom-Csv -InputObject @($headerLine, '__header_probe__'))
if ($headerProbe.Count -eq 0 -or -not ($headerProbe[0].PSObject.Properties.Name -contains 'Email')) {
throw 'The CSV file must contain an Email column.'
}
$members = @(Import-Csv -LiteralPath $CsvPath)
Connect-ToMicrosoftTeams -TenantId $tenantId
$connected = $true
Write-Host 'Reading the current Team member list...'
$existingMembers = Get-ExistingTeamMembers -TeamId $teamId
$addedCount = 0
$skippedCount = 0
$failedCount = 0
foreach ($member in $members) {
$email = [string]$member.Email
if ([string]::IsNullOrWhiteSpace($email)) {
Write-Warning 'Skipping a row with an empty Email value.'
Write-Result -Email '' -Status 'skipped' -Message 'Empty Email value.'
$skippedCount++
continue
}
$email = $email.Trim()
if ($existingMembers.Contains($email)) {
Write-Host "Skipped $email because the user is already a member."
Write-Result -Email $email -Status 'skipped' -Message 'User is already a member.'
$skippedCount++
continue
}
Write-Host "Attempting to add $email to the team..."
try {
Add-TeamUser -GroupId $teamId -User $email -Role Member -ErrorAction Stop
$null = $existingMembers.Add($email)
Write-Host "Successfully added $email."
Write-Result -Email $email -Status 'added' -Message 'Member added successfully.'
$addedCount++
}
catch {
$message = $_.Exception.Message
if ($message -match '(?i)already.*member|already.*exist') {
Write-Host "Skipped $email because the user is already a member."
Write-Result -Email $email -Status 'skipped' -Message 'User is already a member.'
$null = $existingMembers.Add($email)
$skippedCount++
}
else {
Write-Error "Failed to add $email. $message" -ErrorAction Continue
Write-Result -Email $email -Status 'failed' -Message $message
$failedCount++
}
}
}
Write-Host ''
Write-Host 'Done.'
Write-Host "Added: $addedCount"
Write-Host "Skipped: $skippedCount"
Write-Host "Failed: $failedCount"
Write-Host "Results CSV: $OutputPath"
Write-Host "Failed CSV: $FailedOutputPath"
if ($failedCount -gt 0) {
$exitCode = 1
}
}
catch {
Write-Error "Error: $($_.Exception.Message)" -ErrorAction Continue
$exitCode = 1
}
finally {
if ($connected) {
try {
Disconnect-MicrosoftTeams | Out-Null
}
catch {
Write-Warning "Could not disconnect MicrosoftTeams cleanly: $($_.Exception.Message)"
}
}
}
exit $exitCode
Hoàn tất
Bạn chỉ cần làm đúng 7 bước. Sau này, khi có danh sách mới, hãy thay nội dung file student.csv rồi chạy lại bước 6.