diff --git a/powershell/internal/ConvertTo-MtMaesterResults.ps1 b/powershell/internal/ConvertTo-MtMaesterResults.ps1 index d3d3c78c..462f160b 100644 --- a/powershell/internal/ConvertTo-MtMaesterResults.ps1 +++ b/powershell/internal/ConvertTo-MtMaesterResults.ps1 @@ -12,8 +12,39 @@ function ConvertTo-MtMaesterResult { ) function GetTenantName() { - $org = Invoke-MtGraphRequest -RelativeUri 'organization' - return $org.DisplayName + if(Test-MtConnection Graph) { + $org = Invoke-MtGraphRequest -RelativeUri 'organization' + return $org.DisplayName + } elseif (Test-MtConnection Teams) { + $tenant = Get-CsTenant + return $tenant.DisplayName + } else { + return "TenantName (not connected to Graph)" + } + } + + function GetTenantId() { + if(Test-MtConnection Graph) { + $mgContext = Get-MgContext + return $mgContext.TenantId + } elseif (Test-MtConnection Teams) { + $tenant = Get-CsTenant + return $tenant.TenantId + } else { + return "TenantId (not connected to Graph)" + } + } + + function GetAccount() { + if(Test-MtConnection Graph) { + $mgContext = Get-MgContext + return $mgContext.Account + #} elseif (Test-MtConnection Teams) { + # $tenant = Get-CsTenant #ToValidate: N/A + # return $tenant.DisplayName + } else { + return "Account (not connected to Graph)" + } } function GetTestsSorted() { @@ -42,11 +73,15 @@ function ConvertTo-MtMaesterResult { return 'Unknown' } - $mgContext = Get-MgContext + #if(Test-MtConnection Graph) { #ToValidate: Issue with -SkipGraphConnect + # $mgContext = Get-MgContext + #} - $tenantId = $mgContext.TenantId + #$tenantId = $mgContext.TenantId ?? "Tenant ID (not connected to Graph)" + $tenantId = GetTenantId $tenantName = GetTenantName - $account = $mgContext.Account + #$account = $mgContext.Account ?? "Account (not connected to Graph)" + $account = GetAccount $currentVersion = ((Get-Module -Name Maester).Version | Select-Object -Last 1).ToString() $latestVersion = GetMaesterLatestVersion diff --git a/powershell/internal/Get-MtSkippedReason.ps1 b/powershell/internal/Get-MtSkippedReason.ps1 index 47343474..abda8911 100644 --- a/powershell/internal/Get-MtSkippedReason.ps1 +++ b/powershell/internal/Get-MtSkippedReason.ps1 @@ -12,6 +12,7 @@ function Get-MtSkippedReason { "NotConnectedAzure" { "Not connected to Azure. See [Connecting to Azure](https://maester.dev/docs/installation#optional-modules-and-permissions)"; break} "NotConnectedExchange" { "Not connected to Exchange Online. See [Connecting to Exchange Online](https://maester.dev/docs/installation#optional-modules-and-permissions)"; break} "NotConnectedSecurityCompliance" { "Not connected to Security & Compliance. See [Connecting to Security & Compliance](https://maester.dev/docs/installation#optional-modules-and-permissions)"; break} + "NotConnectedTeams" { "Not connected to Teams. See [Connecting to Teams](https://maester.dev/docs/installation#optional-modules-and-permissions)"; break} # Docs? "NotConnectedGraph" { "Not connected to Graph. See [Connect-Maester](https://maester.dev/docs/commands/Connect-Maester#examples)"; break} "NotDotGovDomain" { "This test is only for federal, executive branch, departments and agencies. To override use [Test-MtCisaDmarcAggregateCisa -Force](https://maester.dev/docs/commands/Test-MtCisaDmarcAggregateCisa)"; break} "NotLicensedEntraIDP1" { "This test is for tenants that are licensed for Entra ID P1. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break} diff --git a/powershell/internal/Update-MtMaesterTests.ps1 b/powershell/internal/Update-MtMaesterTests.ps1 index 4af00d91..294eac8b 100644 --- a/powershell/internal/Update-MtMaesterTests.ps1 +++ b/powershell/internal/Update-MtMaesterTests.ps1 @@ -72,7 +72,8 @@ function Update-MtMaesterTests { } $message = "Run `Connect-Maester` to sign in and then run `Invoke-Maester` to start testing." - if (Get-MgContext) { + #if (Get-MgContext) { #ToAdjust: Issue with -SkipGraphConnect + if (Test-MtConnection Graph) { $message = "Run Invoke-Maester to start testing." } diff --git a/powershell/public/Add-MtTestResultDetail.ps1 b/powershell/public/Add-MtTestResultDetail.ps1 index 0f821b6e..52c33008 100644 --- a/powershell/public/Add-MtTestResultDetail.ps1 +++ b/powershell/public/Add-MtTestResultDetail.ps1 @@ -64,7 +64,7 @@ function Add-MtTestResultDetail { [string] $TestName = $____Pester.CurrentTest.ExpandedName, [Parameter(Mandatory = $false)] - [ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotConnectedGraph', 'NotDotGovDomain', 'NotLicensedEntraIDP1', 'NotConnectedSecurityCompliance', + [ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotConnectedGraph', 'NotDotGovDomain', 'NotLicensedEntraIDP1', 'NotConnectedSecurityCompliance', 'NotConnectedTeams', 'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', 'NotLicensedExoDlp', "LicensedEntraIDPremium", 'NotSupported', 'Custom', 'NotLicensedMdo','NotLicensedMdoP1', 'AdvAudit' )] diff --git a/powershell/public/Connect-Maester.ps1 b/powershell/public/Connect-Maester.ps1 index 59b5daa3..8611294b 100644 --- a/powershell/public/Connect-Maester.ps1 +++ b/powershell/public/Connect-Maester.ps1 @@ -99,8 +99,12 @@ function Connect-Maester { [ValidateSet("O365China", "O365Default", "O365GermanyCloud", "O365USGovDoD", "O365USGovGCCHigh")] [string]$ExchangeEnvironmentName = "O365Default", + # The Teams environment to connect to. Default is O365Default. + [ValidateSet("TeamsChina", "TeamsGCCH", "TeamsDOD")] + [string]$TeamsEnvironmentName = $null, #ToValidate: Don't use this parameter, this is the default. + # The services to connect to such as Azure and EXO. Default is Graph. - [ValidateSet("All", "Azure", "ExchangeOnline", "Graph", "SecurityCompliance")] + [ValidateSet("All", "Azure", "ExchangeOnline", "Graph", "SecurityCompliance","Teams")] [string[]]$Service = "Graph" ) @@ -129,7 +133,7 @@ function Connect-Maester { if ($Service -contains "ExchangeOnline" -or $Service -contains "All") { Write-Verbose "Connecting to Microsoft Exchage Online" try { - if ( $UseDeviceCode -and $PSVersionTable.PSEdition -eq "Desktop" ) { + if ($UseDeviceCode -and $PSVersionTable.PSEdition -eq "Desktop") { Write-Host "The Exchange Online module in Windows PowerShell does not support device code flow authentication." -ForegroundColor Red Write-Host "💡Please use the Exchange Online module in PowerShell Core." -ForegroundColor Yellow } elseif ( $UseDeviceCode ) { @@ -182,4 +186,21 @@ function Connect-Maester { } } } + if ($Service -contains "Teams") { #ToValidate: Preview + #if ($Service -contains "Teams" -or $Service -contains "All") { + Write-Verbose "Connecting to Microsoft Teams" + try { + if ($UseDeviceCode) { + Connect-MicrosoftTeams -UseDeviceAuthentication + } elseif ($TeamsEnvironmentName) { + Connect-MicrosoftTeams -TeamsEnvironmentName $TeamsEnvironmentName + } else { + Connect-MicrosoftTeams + #$null = Connect-MicrosoftTeams + } + } catch [Management.Automation.CommandNotFoundException] { + Write-Host "`nThe Teams PowerShell module is not installed. Please install the module using the following command. For more information see https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-install" -ForegroundColor Red + Write-Host "`Install-Module MicrosoftTeams -Scope CurrentUser`n" -ForegroundColor Yellow + } + } } \ No newline at end of file diff --git a/powershell/public/Disconnect-Maester.ps1 b/powershell/public/Disconnect-Maester.ps1 index ca10442e..f7a32b37 100644 --- a/powershell/public/Disconnect-Maester.ps1 +++ b/powershell/public/Disconnect-Maester.ps1 @@ -41,4 +41,8 @@ function Disconnect-Maester { Write-Verbose -Message "Disconnecting from Microsoft Exchange Online." Disconnect-ExchangeOnline } + if($__MtSession.Connections -contains "Teams" -or $__MtSession.Connections -contains "All"){ + Write-Verbose -Message "Disconnecting from Microsoft Teams." + Disconnect-MicrosoftTeams + } } \ No newline at end of file diff --git a/powershell/public/core/Test-MtConnection.ps1 b/powershell/public/core/Test-MtConnection.ps1 index a115c6e8..1b762b0a 100644 --- a/powershell/public/core/Test-MtConnection.ps1 +++ b/powershell/public/core/Test-MtConnection.ps1 @@ -22,7 +22,7 @@ function Test-MtConnection { [CmdletBinding()] param( # Checks if the current session is connected to the specified service - [ValidateSet("All", "Azure", "ExchangeOnline", "Graph", "SecurityCompliance")] + [ValidateSet("All", "Azure", "ExchangeOnline", "Graph", "SecurityCompliance","Teams")] [Parameter(Position = 0, Mandatory = $false)] [string[]]$Service = "Graph" ) @@ -76,5 +76,17 @@ function Test-MtConnection { if (!$isConnected) { $connectionState = $false } } + if ($Service -contains "Teams") { #ToValidate: Preview + #if ($Service -contains "Teams" -or $Service -contains "All") { + $isConnected = $false + try { + $isConnected = $null -ne (Get-CsTenant -ErrorAction SilentlyContinue) + } catch { + Write-Debug "Teams: $false" + } + Write-Verbose "Teams: $isConnected" + if (!$isConnected) { $connectionState = $false } + } + Write-Output $connectionState } \ No newline at end of file diff --git a/tests/Maester/Teams/Test-TeamsMeeting.Tests.ps1 b/tests/Maester/Teams/Test-TeamsMeeting.Tests.ps1 new file mode 100644 index 00000000..8f894069 --- /dev/null +++ b/tests/Maester/Teams/Test-TeamsMeeting.Tests.ps1 @@ -0,0 +1,149 @@ +BeforeDiscovery { + try { + + $TeamsMeetingPolicy = Get-CsTeamsMeetingPolicy + Write-Verbose "Found $($TeamsMeetingPolicy.Count) Teams Meeting policies" + $TeamsMeetingPolicyGlobal = $TeamsMeetingPolicy | Where-Object { $_.Identity -eq "Global" } + Write-Verbose "Filtered $( $TeamsMeetingPolicyGlobal.Count) Global Teams Meeting policy" + + } catch { + Write-Verbose "Session is not established, run Connect-MicrosoftTeams before requesting access token" + } +} + +Describe "Teams Meeting policies" -Tag "Maester", "Teams", "MeetingPolicy" { + + + It "Configure which users are allowed to present in Teams meetings" -Tag "AllowParticipantGiveRequestControl" { + + $portalLink_MeetingPolicy = "https://admin.teams.microsoft.com/policies/meetings" + + if (!(Test-MtConnection Teams)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedTeams + return $null + } + + $result = $TeamsMeetingPolicyGlobal.AllowParticipantGiveRequestControl + + if ($result -eq $false) { + $testResultMarkdown = "Well done. AllowParticipantGiveRequestControl is $($result)`n`n" + } else { + $testResultMarkdown = "AllowParticipantGiveRequestControl in [Meeting policies]($portalLink_MeetingPolicy) should be False and is $($result) `n`n" + } + $testDetailsMarkdown = "Only allow users with presenter rights to share content during meetings. Restricting who can present limits meeting disruptions and reduces the risk of unwanted or inappropriate content being shared." + Add-MtTestResultDetail -Description $testDetailsMarkdown -Result $testResultMarkdown + + $result | Should -Be $false -Because "AllowParticipantGiveRequestControl should be False" + } + + It "Only invited users should be automatically admitted to Teams meetings" -Tag "AutoAdmittedUsers" { + #($TeamsMeetingPolicyGlobal.AutoAdmittedUsers -eq "InvitedUsers") + + $portalLink_MeetingPolicy = "https://admin.teams.microsoft.com/policies/meetings" + + if (!(Test-MtConnection Teams)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedTeams + return $null + } + + $result = $TeamsMeetingPolicyGlobal.AutoAdmittedUsers + + if ($result -eq "InvitedUsers") { + $testResultMarkdown = "Well done. AutoAdmittedUsers is $($result)`n`n" + } else { + $testResultMarkdown = "AutoAdmittedUsers in [Meeting policies]($portalLink_MeetingPolicy) should be InvitedUsers and is $($result) `n`n" + } + $testDetailsMarkdown = "Users who aren’t invited to a meeting shouldn’t be let in automatically, because it increases the risk of data leaks, inappropriate content being shared, or malicious actors joining. If only invited users are automatically admitted, then users who weren’t invited will be sent to a meeting lobby. The host can then decide whether or not to let them in." + Add-MtTestResultDetail -Description $testDetailsMarkdown -Result $testResultMarkdown + + $result | Should -Be "InvitedUsers" -Because "AutoAdmittedUsers should be InvitedUsers" + } + + It "Restrict anonymous users from joining meetings" -Tag "AllowAnonymousUsersToJoinMeeting" { + + $portalLink_MeetingPolicy = "https://admin.teams.microsoft.com/policies/meetings" + + if (!(Test-MtConnection Teams)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedTeams + return $null + } + + $result = $TeamsMeetingPolicyGlobal.AllowAnonymousUsersToJoinMeeting + + if ($result -eq $false) { + $testResultMarkdown = "Well done. AllowAnonymousUsersToJoinMeeting is $($result)`n`n" + } else { + $testResultMarkdown = "AllowAnonymousUsersToJoinMeeting in [Meeting policies]($portalLink_MeetingPolicy) should be False and is $($result) `n`n" + } + $testDetailsMarkdown = "By restricting anonymous users from joining Microsoft Teams meetings, you have full control over meeting access. Anonymous users may not be from your organization and could have joined for malicious purposes, such as gaining information about your organization through conversations." + Add-MtTestResultDetail -Description $testDetailsMarkdown -Result $testResultMarkdown + + $result | Should -Be $false -Because "AllowAnonymousUsersToJoinMeeting should be False" + } + + It "Restrict anonymous users from starting Teams meetings" -Tag "AllowAnonymousUsersToStartMeeting" { + + $portalLink_MeetingPolicy = "https://admin.teams.microsoft.com/policies/meetings" + + if (!(Test-MtConnection Teams)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedTeams + return $null + } + + $result = $TeamsMeetingPolicyGlobal.AllowAnonymousUsersToStartMeeting + + if ($result -eq $false) { + $testResultMarkdown = "Well done. AllowAnonymousUsersToStartMeeting is $($result)`n`n" + } else { + $testResultMarkdown = "AllowAnonymousUsersToStartMeeting in [Meeting policies]($portalLink_MeetingPolicy) should be False and is $($result) `n`n" + } + $testDetailsMarkdown = "If anonymous users are allowed to start meetings, they can admit any users from the lobbies, authenticated or otherwise. Anonymous users haven’t been authenticated, which can increase the risk of data leakage." + Add-MtTestResultDetail -Description $testDetailsMarkdown -Result $testResultMarkdown + + $result | Should -Be $false -Because "AllowAnonymousUsersToStartMeeting should be False" + } + + It "Limit external participants from having control in a Teams meeting" -Tag "AllowExternalParticipantGiveRequestControl" { + + $portalLink_MeetingPolicy = "https://admin.teams.microsoft.com/policies/meetings" + + if (!(Test-MtConnection Teams)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedTeams + return $null + } + + $result = $TeamsMeetingPolicyGlobal.AllowExternalParticipantGiveRequestControl + + if ($result -eq $false) { + $testResultMarkdown = "Well done. AllowExternalParticipantGiveRequestControl is $($result)`n`n" + } else { + $testResultMarkdown = "AllowExternalParticipantGiveRequestControl in [Meeting policies]($portalLink_MeetingPolicy) should be False and is $($result) `n`n" + } + $testDetailsMarkdown = "External participants are users that are outside your organization. Limiting their permission to share content, add new users, and more protects your organization’s information from data leaks, inappropriate content being shared, or malicious actors joining the meeting." + Add-MtTestResultDetail -Description $testDetailsMarkdown -Result $testResultMarkdown + + $result | Should -Be $false -Because "AllowExternalParticipantGiveRequestControl should be False" + } + + It "Restrict dial-in users from bypassing a meeting lobby " -Tag "AllowPSTNUsersToBypassLobby" { + + $portalLink_MeetingPolicy = "https://admin.teams.microsoft.com/policies/meetings" + + if (!(Test-MtConnection Teams)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedTeams + return $null + } + + $result = $TeamsMeetingPolicyGlobal.AllowPSTNUsersToBypassLobby + + if ($result -eq $false) { + $testResultMarkdown = "Well done. AllowPSTNUsersToBypassLobby is $($result)`n`n" + } else { + $testResultMarkdown = "AllowPSTNUsersToBypassLobby in [Meeting policies]($portalLink_MeetingPolicy) should be False and is $($result) `n`n" + } + $testDetailsMarkdown = "Dial-in users aren’t authenticated though the Teams app. Increase the security of your meetings by preventing these unknown users from bypassing the lobby and immediately joining the meeting." + Add-MtTestResultDetail -Description $testDetailsMarkdown -Result $testResultMarkdown + + $result | Should -Be $false -Because "AllowPSTNUsersToBypassLobby should be False" + } +} diff --git a/website/docs/commands/Connect-Maester.mdx b/website/docs/commands/Connect-Maester.mdx index 746472fc..ee6c7922 100644 --- a/website/docs/commands/Connect-Maester.mdx +++ b/website/docs/commands/Connect-Maester.mdx @@ -243,6 +243,23 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -TeamsEnvironmentName + +The Teams environment to connect to. +Default is $null + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: $null +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Service The services to connect to such as Azure and EXO. diff --git a/website/docs/connect-maester/connect-maester-advanced.md b/website/docs/connect-maester/connect-maester-advanced.md index c2145949..9017fd1a 100644 --- a/website/docs/connect-maester/connect-maester-advanced.md +++ b/website/docs/connect-maester/connect-maester-advanced.md @@ -114,4 +114,17 @@ The Microsoft Exchange Online and Security & Compliance PowerShell Modules provi Connect-ExchangeOnline -Certificate $cert -AppID $applicationId -Organization $moera -ShowBanner:$false Connect-IPPSSession -Certificate $cert -AppID $applicationId -Organization $moera -ShowBanner:$false -``` \ No newline at end of file +``` + +### Microsoft Teams PowerShell Module + +The Microsoft Teams PowerShell Module supports both interactive and non-interactive [authentication methods](https://learn.microsoft.com/powershell/module/teams/connect-microsoftteams?view=teams-ps). For interactive sessions, you can use the standard login prompt. For non-interactive use, such as in automation scenarios, service principal authentication is recommended. + +```powershell +# Interactive +Connect-MicrosoftTeams + +# Non-Interactive (Service Principal) +$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\exampleCert.pfx",$password) +Connect-MicrosoftTeams -Certificate $cert -ApplicationId $applicationId -TenantId $tenantId +``` diff --git a/website/docs/connect-maester/readme.md b/website/docs/connect-maester/readme.md index 417792e1..1a40a57b 100644 --- a/website/docs/connect-maester/readme.md +++ b/website/docs/connect-maester/readme.md @@ -78,11 +78,11 @@ The `-DeviceCode` switch allows you to sign in using the device code flow. This Connect-Maester -UseDeviceCode ``` -### Connect to Azure and Exchange Online +### Connect to Azure, Exchange Online and Teams -`Connect-Maester` also provides options to connect to Azure and Exchange Online for running tests that use the Azure PowerShell and Exchange Online PowerShell modules. +`Connect-Maester` also provides options to connect to Azure, Exchange Online adn Teams for running tests that use the Azure PowerShell, Exchange Online PowerShell or Teams PowerShell modules. -The `-All` switch can be used to connect to all the services used by the Maester tests. This includes Microsoft Graph, Azure, Exchange Online and Security Compliance. +The `-All` switch can be used to connect to all the services used by the Maester tests. This includes Microsoft Graph, Azure, Exchange Online, Security Compliance and Microsoft Teams. ```powershell Connect-Maester -Service All @@ -91,7 +91,7 @@ Connect-Maester -Service All If you need to connect to just a subset of the services you can specifiy them using the `-Service` parameter. ```powershell -Connect-Maester -Service Azure,Graph +Connect-Maester -Service Azure,Graph,Teams ``` ### Connect to US Government, US DoD, China and Germany and other clouds