Functions/Public/New-CT365Teams.ps1

<#
.SYNOPSIS
Creates new Microsoft Teams and associated channels based on data from an Excel file.

.DESCRIPTION
The New-CT365Teams function connects to Microsoft Teams via PnP PowerShell, reads team and channel information from an Excel file, and creates new Teams and channels as specified. It supports retry logic for team and channel creation and allows specifying a default owner. The function requires the PnP.PowerShell, ImportExcel, and PSFramework modules.

.PARAMETER FilePath
Specifies the path to the Excel file containing the Teams and channel data. The file must be in .xlsx format.

.PARAMETER AdminUrl
Specifies the SharePoint admin URL for the tenant. The URL must match the format 'tenant.sharepoint.com'.

.PARAMETER DefaultOwnerUPN
Specifies the default owner's User Principal Name (UPN) for the Teams and channels.

.EXAMPLE
PS> New-CT365Teams -FilePath "C:\TeamsData.xlsx" -AdminUrl "contoso.sharepoint.com" -DefaultOwnerUPN "admin@contoso.com"

This example creates Teams and channels based on the data in 'C:\TeamsData.xlsx', using 'admin@contoso.com' as the default owner if none is specified in the Excel file.

.NOTES
- Requires the PnP.PowerShell, ImportExcel, and PSFramework modules.
- The Excel file should have a worksheet named 'teams' with appropriate columns for team and channel data.
- The function includes error handling and logging using PSFramework.

#>

function New-CT365Teams {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateScript({
                # First, check if the file has a valid Excel extension (.xlsx)
                if (-not(([System.IO.Path]::GetExtension($psitem)) -match "\.(xlsx)$")) {
                    throw "The file path '$PSitem' does not have a valid Excel format. Please make sure to specify a valid file with a .xlsx extension and try again."
                }
        
                # Then, check if the file exists
                if (-not([System.IO.File]::Exists($psitem))) {
                    throw "The file path '$PSitem' does not lead to an existing file. Please verify the 'FilePath' parameter and ensure that it points to a valid file (folders are not allowed)."
                }
        
                # Return true if both conditions are met
                $true
            })]
        [string]$FilePath,

        [Parameter(Mandatory)]
        [ValidateScript({
                if ($_ -match '^[a-zA-Z0-9]+\.sharepoint\.[a-zA-Z0-9]+$') {
                    $true
                }
                else {
                    throw "The URL $_ does not match the required format."
                }
            })]
        [string]$AdminUrl,


        [Parameter(Mandatory)]
        [string]$DefaultOwnerUPN
    )

    # Check and import required modules
    $requiredModules = @('PnP.PowerShell', 'ImportExcel', 'PSFramework')
    foreach ($module in $requiredModules) {
        try {
            if (-not (Get-Module -ListAvailable -Name $module)) {
                throw "Module $module is not installed."
            }
            Import-Module $module
        }
        catch {
            Write-PSFMessage -Level Warning -Message "[$(Get-Date -Format 'u')] $_.Exception.Message"
            return
        }
    }

    try {
        $teamsData = Import-Excel -Path $FilePath -WorksheetName "teams"
        $existingTeams = Get-PnPTeamsTeam
    }
    catch {
        Write-PSFMessage -Level Error -Message "[$(Get-Date -Format 'u')] Failed to import data from Excel or retrieve existing teams: $($_.Exception.Message)"
        return
    }

    foreach ($teamRow in $teamsData) {
        try {
            $teamOwnerUPN = if ($teamRow.TeamOwnerUPN) { $teamRow.TeamOwnerUPN } else { $DefaultOwnerUPN }
            $existingTeam = $existingTeams | Where-Object { $_.DisplayName -eq $teamRow.TeamName }

            if ($existingTeam) {
                Write-PSFMessage -Level Host -Message "[$(Get-Date -Format 'u')] Team $($teamRow.TeamName) already exists. Skipping creation."
                continue
            }

            $retryCount = 0
            $teamCreationSuccess = $false
            do {
                try {
                    $teamId = New-PnPTeamsTeam -DisplayName $teamRow.TeamName -Description $teamRow.TeamDescription -Visibility $teamRow.TeamType -Owners $teamOwnerUPN
                    if (Verify-CT365TeamsCreation -teamName $teamRow.TeamName) {
                        Write-PSFMessage -Level Host -Message "[$(Get-Date -Format 'u')] Verified creation of Team: $($teamRow.TeamName)"
                        $teamCreationSuccess = $true
                        break
                    }
                    else {
                        Write-PSFMessage -Level Warning -Message "[$(Get-Date -Format 'u')] Team $($teamRow.TeamName) creation reported but not verified. Retrying..."
                    }
                }
                catch {
                    Write-PSFMessage -Level Warning -Message "[$(Get-Date -Format 'u')] Attempt $retryCount to create team $($teamRow.TeamName) failed: $($_.Exception.Message)"
                }
                $retryCount++
                Start-Sleep -Seconds 5
            } while ($retryCount -lt 5)

            if (-not $teamCreationSuccess) {
                Write-PSFMessage -Level Error -Message "[$(Get-Date -Format 'u')] Failed to create and verify Team: $($teamRow.TeamName) after multiple retries."
                continue
            }

            for ($i = 1; $i -le 4; $i++) {
                $channelName = $teamRow."Channel${i}Name"
                $channelType = $teamRow."Channel${i}Type"
                $channelDescription = $teamRow."Channel${i}Description"
                $channelOwnerUPN = if ($teamRow."Channel${i}OwnerUPN") { $teamRow."Channel${i}OwnerUPN" } else { $DefaultOwnerUPN }

                if ($channelName -and $channelType) {
                    $retryCount = 1
                    $channelCreationSuccess = $false
                    do {
                        try {
                            Add-PnPTeamsChannel -Team $teamId -DisplayName $channelName -Description $channelDescription -ChannelType $channelType -OwnerUPN $channelOwnerUPN
                            Write-PSFMessage -Level Host -Message "[$(Get-Date -Format 'u')] Created Channel: $channelName in Team: $($teamRow.TeamName) with Type: $channelType and Description: $channelDescription"
                            $channelCreationSuccess = $true
                            break
                        }
                        catch {
                            Write-PSFMessage -Level Warning -Message "[$(Get-Date -Format 'u')] Attempt $retryCount to create channel $channelName in Team: $($teamRow.TeamName) failed: $($_.Exception.Message)"
                            $retryCount++
                            Start-Sleep -Seconds 10
                        }
                    } while ($retryCount -lt 5)

                    if (-not $channelCreationSuccess) {
                        Write-PSFMessage -Level Error -Message "[$(Get-Date -Format 'u')] Failed to create Channel: $channelName in Team: $($teamRow.TeamName) after multiple retries."
                    }
                }
            }
        }
        catch {
            Write-PSFMessage -Level Error -Message "[$(Get-Date -Format 'u')] Error processing team $($teamRow.TeamName): $($_.Exception.Message)"
        }
    }

    try {
        Disconnect-PnPOnline
    }
    catch {
        Write-PSFMessage -Level Error -Message "[$(Get-Date -Format 'u')] Error disconnecting PnP Online: $($_.Exception.Message)"
    }
}