Windows · Dành cho người mới

Thêm nhiều thành viên vào Microsoft Teams

Làm lần lượt từng bước bên dưới. Bạn không cần biết lập trình. Trang này đã chứa sẵn toàn bộ file teams.ps1 và có nút sao chép.

Bắt đầu từ bước 1
Trước khi bắt đầu: tài khoản Microsoft của bạn phải có quyền thêm thành viên vào Team. Nếu công ty hoặc trường học chặn quyền này, hãy nhờ quản trị viên Microsoft 365 hỗ trợ.

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=.
Lưu ý: liên kết cuộc họp (meeting link) không dùng được. Bạn cần liên kết của cả Team.
1

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.

  1. Mở File Explorer (biểu tượng thư mục màu vàng).
  2. Mở thư mục Documents hoặc Downloads.
  3. Nhấp chuột phải vào vùng trống → NewFolder.
  4. Đặt tên thư mục là TeamsMembers, rồi mở thư mục đó.
teams.ps1Chương trình sẽ chạy
student.csvDanh sách email của bạn
2

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

  1. Nhấn nút Tải file teams.ps1 bên dưới.
  2. Trình duyệt sẽ tải một file tên teams.ps1.
  3. 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

  1. Đi tới phần Toàn bộ mã nguồn ở cuối trang.
  2. Nhấn Sao chép toàn bộ.
  3. Mở Notepad, dán code bằng Ctrl + V.
  4. Chọn File → Save As. Ở ô tên file, nhập chính xác teams.ps1.
  5. Save as type, chọn All files (*.*); ở Encoding, chọn UTF-8; rồi lưu vào thư mục TeamsMembers.
Nếu file có tên teams.ps1.txt, hãy đổi tên thành teams.ps1. Windows có thể hỏi xác nhận; chọn Yes.
3

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:

  1. Nhấn nút Start của Windows.
  2. PowerShell, sau đó mở Windows PowerShell. Không bắt buộc mở bằng quyền Administrator.
  3. Sao chép lệnh dưới đây, dán vào PowerShell và nhấn Enter.
Lệnh cài module MicrosoftTeams
Install-Module -Name MicrosoftTeams -Scope CurrentUser -Force -AllowClobber
  1. Nếu PowerShell hỏi cài NuGet provider, nhập Y rồi nhấn Enter.
  2. Nếu hỏi có tin tưởng PSGallery hay không, nhập Y hoặc A, rồi nhấn Enter.
  3. Đợ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.
  4. Dùng lệnh bên dưới để kiểm tra:
Lệnh 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.

4

Chuẩn bị danh sách CSV

Dòng đầu phải có đúng chữ Email.

Nếu dùng Excel

  1. Mở Excel và tạo bảng mới.
  2. Ô A1 nhập Email.
  3. Từ ô A2 trở xuống, mỗi dòng nhập một địa chỉ email.
  4. Chọn File → Save As.
  5. Đặt tên student.csv; loại file chọn CSV UTF-8 (Comma delimited) (*.csv).
  6. 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.
Mẫu nội dung student.csv
Email
student1@example.com
student2@example.com
Quan trọng: không dùng tiêu đề như “E-mail”, “Mail” hoặc “Email Address”. Tiêu đề phải là đúng một từ Email.
5

Lấy liên kết của Team

Không dùng liên kết của cuộc họp.

  1. Mở Microsoft Teams và vào đúng Team muốn thêm người.
  2. Nhấn dấu ba chấm cạnh tên Team.
  3. 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.
  4. Dán tạm vào Notepad và kiểm tra liên kết có đoạn groupId=.
Nếu không thấy mục sao chép liên kết hoặc không có quyền thêm thành viên, hãy liên hệ chủ sở hữu Team hay quản trị viên.
6

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.

  1. Mở thư mục TeamsMembers trong File Explorer.
  2. Nhấp vào thanh địa chỉ ở phía trên cửa sổ (nơi hiện đường dẫn thư mục).
  3. 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.
  4. Sao chép lệnh sau, dán vào PowerShell và nhấn Enter.
Lệnh chạy chương trình
powershell -NoProfile -ExecutionPolicy Bypass -File .\teams.ps1
  1. 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.
  2. Khi thấy Please enter the CSV file path, nhập .\student.csv rồi nhấn Enter.
  3. Khi thấy dòng Opening the Microsoft Teams sign-in window..., một cửa sổ đăng nhập Microsoft sẽ mở ra.
  4. 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.
  5. 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.
Bạn có thể thấy các dòng Attempting to add...Successfully added.... Đây là hoạt động bình thường.
7

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
AddedSố người đã được thêm thành công.
SkippedDòng trống hoặc người đã có trong Team; không cần thêm lại.
FailedKhô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.

teams.ps1 · PowerShell
[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.