diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b4892eab..d2d3145d4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change log for Microsoft365DSC +# 1.20.1216.1 + +* AADConditionalAccessPolicy + * Initial Release; +* EXOSafeLinksRule + * Fixed typo in a try/catch clause; +* O365User + * Added support for removing existing users with + Ensure = 'Absent'; +* TeamsChannelTab + * Initial Release; + # 1.20.1209.1 * IntuneAppProtectionPolicyiOS diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 new file mode 100644 index 0000000000..0a0067f6fa --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 @@ -0,0 +1,1826 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + [ValidateSet('disabled', 'enabled', 'enabledForReportingButNotEnforced')] + $State, + + #ConditionalAccessApplicationCondition + [Parameter()] + [System.String[]] + $IncludeApplications, + + [Parameter()] + [System.String[]] + $ExcludeApplications, + + [Parameter()] + [System.String[]] + $IncludeUserActions, + + #ConditionalAccessUserCondition + [Parameter()] + [System.String[]] + $IncludeUsers, + + [Parameter()] + [System.String[]] + $ExcludeUsers, + + [Parameter()] + [System.String[]] + $IncludeGroups, + + [Parameter()] + [System.String[]] + $ExcludeGroups, + + [Parameter()] + [System.String[]] + $IncludeRoles, + + [Parameter()] + [System.String[]] + $ExcludeRoles, + + #ConditionalAccessPlatformCondition + [Parameter()] + [System.String[]] + $IncludePlatforms, + + [Parameter()] + [System.String[]] + $ExcludePlatforms, + + #ConditionalAccessLocationCondition + [Parameter()] + [System.String[]] + $IncludeLocations, + + [Parameter()] + [System.String[]] + $ExcludeLocations, + + #ConditionalAccessDevicesCondition + [Parameter()] + [System.String[]] + $IncludeDeviceStates, + + [Parameter()] + [System.String[]] + $ExcludeDeviceStates, + + #Further conditions + [Parameter()] + [System.String[]] + $UserRiskLevels, + + [Parameter()] + [System.String[]] + $SignInRiskLevels, + + [Parameter()] + [System.String[]] + $ClientAppTypes, + + #ConditionalAccessGrantControls + [Parameter()] + [ValidateSet('AND', 'OR')] + [System.String] + $GrantControlOperator, + + [Parameter()] + [System.String[]] + $BuiltInControls, + + #ConditionalAccessSessionControls + [Parameter()] + [System.Boolean] + $ApplicationEnforcedRestrictionsIsEnabled, + + [Parameter()] + [System.Boolean] + $CloudAppSecurityIsEnabled, + + [Parameter()] + [System.String] + $CloudAppSecurityType, + + [Parameter()] + [System.Int32] + $SignInFrequencyValue, + + [Parameter()] + [ValidateSet('Days', 'Hours', '')] + [System.String] + $SignInFrequencyType, + + [Parameter()] + [System.Boolean] + $SignInFrequencyIsEnabled, + + [Parameter()] + [ValidateSet('Always', 'Never', '')] + [System.String] + $PersistentBrowserMode, + + [Parameter()] + [System.Boolean] + $PersistentBrowserIsEnabled, + + #generic + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $GlobalAdminAccount, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + + Write-Verbose -Message "Getting configuration of AzureAD Conditional Access Policy" + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $ConnectionMode = New-M365DSCConnection -Platform 'AzureAD' -InboundParameters $PSBoundParameters + + if ($PSBoundParameters.ContainsKey("Id")) + { + Write-Verbose -Message "PolicyID was specified" + try + { + $Policy = Get-AzureADMSConditionalAccessPolicy -PolicyId $Id + } + catch + { + $Policy = Get-AzureADMSConditionalAccessPolicy | Where-Object { $_.DisplayName -eq $DisplayName } + if ($Policy.Length -gt 1) + { + throw "Duplicate CA Policies named $DisplayName exist in tenant" + } + } + } + else + { + Write-Verbose -Message "Id was NOT specified" + ## Can retreive multiple CA Policies since displayname is not unique + $Policy = Get-AzureADMSConditionalAccessPolicy | Where-Object { $_.DisplayName -eq $DisplayName } + if ($Policy.Length -gt 1) + { + throw "Duplicate CA Policies named $DisplayName exist in tenant" + } + } + + if ($null -eq $Policy) + { + $currentValues = $PSBoundParameters + $currentValues.Ensure = "Absent" + return $currentValues + } + else + { + Write-Verbose -Message "Get-TargetResource: Found existing Conditional Access policy" + $PolicyDisplayName = $Policy.DisplayName + + Write-Verbose -Message "Get-TargetResource: Process IncludeUsers" + #translate IncludeUser GUIDs to UPN, except id value is GuestsOrExternalUsers or All + $IncludeUsers = $null + if ($Policy.Conditions.Users.IncludeUsers) + { + $IncludeUsers = @() + foreach ($IncludeUserGUID in $Policy.Conditions.Users.IncludeUsers) + { + if ($IncludeUserGUID -notin "GuestsOrExternalUsers", "All") + { + $IncludeUser = $null + try + { + $IncludeUser = (Get-AzureADUser -ObjectId $IncludeUserGUID).userprincipalname + } + catch + { + try + { + Write-Verbose -Message $_ + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message "Couldn't find user $IncludeUserGUID , that is defined in policy $PolicyDisplayName" -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + if ($IncludeUser) + { + $IncludeUsers += $IncludeUser + } + } + else + { + $IncludeUsers += $IncludeUserGUID + } + } + } + + Write-Verbose -Message "Get-TargetResource: Process ExcludeUsers" + #translate ExcludeUser GUIDs to UPN, except id value is GuestsOrExternalUsers or All + $ExcludeUsers = $null + if ($Policy.Conditions.Users.ExcludeUsers) + { + $ExcludeUsers = @() + foreach ($ExcludeUserGUID in $Policy.Conditions.Users.ExcludeUsers) + { + if ($ExcludeUserGUID -notin "GuestsOrExternalUsers", "All") + { + $ExcludeUser = $null + try + { + $ExcludeUser = (Get-AzureADUser -ObjectId $ExcludeUserGUID).userprincipalname + } + catch + { + $Message = "Couldn't find user $ExcludeUserGUID , that is defined in policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + if ($ExcludeUser) + { + $ExcludeUsers += $ExcludeUser + } + } + else + { + $ExcludeUsers += $ExcludeUserGUID + } + } + } + + Write-Verbose -Message "Get-TargetResource: Process IncludeGroups" + #translate IncludeGroup GUIDs to DisplayName + $IncludeGroups = $null + if ($Policy.Conditions.Users.IncludeGroups) + { + $IncludeGroups = @() + foreach ($IncludeGroupGUID in $Policy.Conditions.Users.IncludeGroups) + { + $IncludeGroup = $null + try + { + $IncludeGroup = (Get-AzureADGroup -ObjectId $IncludeGroupGUID).displayname + } + catch + { + $Message = "Couldn't find Group $IncludeGroupGUID , that is defined in policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + if ($IncludeGroup) + { + $IncludeGroups += $IncludeGroup + } + } + } + + Write-Verbose -Message "Get-TargetResource: Process ExcludeGroups" + #translate ExcludeGroup GUIDs to DisplayName + $ExcludeGroups = $null + if ($Policy.Conditions.Users.ExcludeGroups) + { + $ExcludeGroups = @() + foreach ($ExcludeGroupGUID in $Policy.Conditions.Users.ExcludeGroups) + { + $ExcludeGroup = $null + try + { + $ExcludeGroup = (Get-AzureADGroup -ObjectId $ExcludeGroupGUID).displayname + } + catch + { + $Message = "Couldn't find Group $ExcludeGroupGUID , that is defined in policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + if ($ExcludeGroup) + { + $ExcludeGroups += $ExcludeGroup + } + } + } + + + $IncludeRoles = $null + $ExcludeRoles = $null + #translate role template guids to role name + if ($Policy.Conditions.Users.IncludeRoles -or $Policy.Conditions.Users.ExcludeRoles) + { + Write-Verbose -Message "Get-TargetResource: Role condition defined, processing" + #build role translation table + $rolelookup = @{} + foreach ($role in Get-AzureADDirectoryRoleTemplate) + { + $rolelookup[$role.ObjectId] = $role.DisplayName + } + + Write-Verbose -Message "Get-TargetResource: Processing IncludeRoles" + if ($Policy.Conditions.Users.IncludeRoles) + { + $IncludeRoles = @() + foreach ($IncludeRoleGUID in $Policy.Conditions.Users.IncludeRoles) + { + if ($null -eq $rolelookup[$IncludeRoleGUID]) + { + $Message = "Couldn't find role $IncludeRoleGUID , couldn't add to policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $IncludeRoles += $rolelookup[$IncludeRoleGUID] + } + } + } + + Write-Verbose -Message "Get-TargetResource: Processing ExcludeRoles" + if ($Policy.Conditions.Users.ExcludeRoles) + { + $ExcludeRoles = @() + foreach ($ExcludeRoleGUID in $Policy.Conditions.Users.ExcludeRoles) + { + if ($null -eq $rolelookup[$ExcludeRoleGUID]) + { + $Message = "Couldn't find role $ExcludeRoleGUID , couldn't add to policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $ExcludeRoles += $rolelookup[$ExcludeRoleGUID] + } + } + } + + } + + $IncludeLocations = $null + $ExcludeLocations = $null + #translate Location template guids to Location name + if ($Policy.Conditions.Locations.IncludeLocations -or $Policy.Conditions.Locations.ExcludeLocations) + { + Write-Verbose -Message "Get-TargetResource: Location condition defined, processing" + #build Location translation table + $Locationlookup = @{} + foreach ($Location in Get-AzureADMSNamedLocationPolicy) + { + $Locationlookup[$Location.Id] = $Location.DisplayName + } + + Write-Verbose -Message "Get-TargetResource: Processing IncludeLocations" + if ($Policy.Conditions.Locations.IncludeLocations) + { + $IncludeLocations = @() + foreach ($IncludeLocationGUID in $Policy.Conditions.Locations.IncludeLocations) + { + if ($IncludeLocationGUID -in "All", "AllTrusted") + { + $IncludeLocations += $IncludeLocationGUID + } + elseif ($null -eq $Locationlookup[$IncludeLocationGUID]) + { + $Message = "Couldn't find Location $IncludeLocationGUID , couldn't add to policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $IncludeLocations += $Locationlookup[$IncludeLocationGUID] + } + } + } + + Write-Verbose -Message "Get-TargetResource: Processing ExcludeLocations" + if ($Policy.Conditions.Locations.ExcludeLocations) + { + $ExcludeLocations = @() + foreach ($ExcludeLocationGUID in $Policy.Conditions.Locations.ExcludeLocations) + { + if ($ExcludeLocationGUID -in "All", "AllTrusted") + { + $ExcludeLocations += $ExcludeLocationGUID + } + elseif ($null -eq $Locationlookup[$ExcludeLocationGUID]) + { + $Message = "Couldn't find Location $ExcludeLocationGUID , couldn't add to policy $PolicyDisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $ExcludeLocations += $Locationlookup[$ExcludeLocationGUID] + } + } + } + + + } + + $result = @{ + DisplayName = $Policy.DisplayName + Id = $Policy.Id + State = $Policy.State + IncludeApplications = [System.String[]]$Policy.Conditions.Applications.IncludeApplications + #no translation of Application GUIDs + ExcludeApplications = [System.String[]]$Policy.Conditions.Applications.ExcludeApplications + #no translation of GUIDs + IncludeUserActions = [System.String[]]$Policy.Conditions.Applications.IncludeUserActions + #no translation needed + IncludeUsers = $IncludeUsers + ExcludeUsers = $ExcludeUsers + IncludeGroups = $IncludeGroups + ExcludeGroups = $ExcludeGroups + IncludeRoles = $IncludeRoles + ExcludeRoles = $ExcludeRoles + + IncludePlatforms = [System.String[]]$Policy.Conditions.Platforms.IncludePlatforms + #no translation needed + ExcludePlatforms = [System.String[]]$Policy.Conditions.Platforms.ExcludePlatforms + #no translation needed + IncludeLocations = $IncludeLocations + ExcludeLocations = $ExcludeLocations + IncludeDeviceStates = [System.String[]]$Policy.Conditions.Devices.IncludeDeviceStates + #no translation needed + ExcludeDeviceStates = [System.String[]]$Policy.Conditions.Devices.ExcludeDeviceStates + #no translation needed + UserRiskLevels = [System.String[]]$Policy.Conditions.UserRiskLevels + #no translation needed + SignInRiskLevels = [System.String[]]$Policy.Conditions.SignInRiskLevels + #no translation needed + ClientAppTypes = [System.String[]]$Policy.Conditions.ClientAppTypes + #no translation needed + GrantControlOperator = $Policy.GrantControls._Operator + #no translation or conversion needed + BuiltInControls = [System.String[]]$Policy.GrantControls.BuiltInControls + #no translation needed + ApplicationEnforcedRestrictionsIsEnabled = $Policy.SessionControls.ApplicationEnforcedRestrictions.IsEnabled + #no translation or conversion needed + CloudAppSecurityIsEnabled = $Policy.SessionControls.CloudAppSecurity.IsEnabled + #no translation or conversion needed + CloudAppSecurityType = [System.String]$Policy.SessionControls.CloudAppSecurity.CloudAppSecurityType + #no translation needed + SignInFrequencyValue = $Policy.SessionControls.SignInFrequency.Value + #no translation or conversion needed + SignInFrequencyType = [System.String]$Policy.SessionControls.SignInFrequency.Type + #no translation needed + SignInFrequencyIsEnabled = $Policy.SessionControls.SignInFrequency.IsEnabled + #no translation or conversion needed + PersistentBrowserMode = [System.String]$Policy.SessionControls.PersistentBrowser.Mode + #no translation needed + PersistentBrowserIsEnabled = $Policy.SessionControls.PersistentBrowser.IsEnabled + #no translation or conversion needed + #Standard part + Ensure = "Present" + GlobalAdminAccount = $GlobalAdminAccount + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + } + Write-Verbose -Message "Get-TargetResource Result: `n $(Convert-M365DscHashtableToString -Hashtable $result)" + return $result + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + [ValidateSet('disabled', 'enabled', 'enabledForReportingButNotEnforced')] + $State, + + #ConditionalAccessApplicationCondition + [Parameter()] + [System.String[]] + $IncludeApplications, + + [Parameter()] + [System.String[]] + $ExcludeApplications, + + [Parameter()] + [System.String[]] + $IncludeUserActions, + + #ConditionalAccessUserCondition + [Parameter()] + [System.String[]] + $IncludeUsers, + + [Parameter()] + [System.String[]] + $ExcludeUsers, + + [Parameter()] + [System.String[]] + $IncludeGroups, + + [Parameter()] + [System.String[]] + $ExcludeGroups, + + [Parameter()] + [System.String[]] + $IncludeRoles, + + [Parameter()] + [System.String[]] + $ExcludeRoles, + + #ConditionalAccessPlatformCondition + [Parameter()] + [System.String[]] + $IncludePlatforms, + + [Parameter()] + [System.String[]] + $ExcludePlatforms, + + #ConditionalAccessLocationCondition + [Parameter()] + [System.String[]] + $IncludeLocations, + + [Parameter()] + [System.String[]] + $ExcludeLocations, + + #ConditionalAccessDevicesCondition + [Parameter()] + [System.String[]] + $IncludeDeviceStates, + + [Parameter()] + [System.String[]] + $ExcludeDeviceStates, + + #Further conditions + [Parameter()] + [System.String[]] + $UserRiskLevels, + + [Parameter()] + [System.String[]] + $SignInRiskLevels, + + [Parameter()] + [System.String[]] + $ClientAppTypes, + + #ConditionalAccessGrantControls + [Parameter()] + [ValidateSet('AND', 'OR')] + [System.String] + $GrantControlOperator, + + [Parameter()] + [System.String[]] + $BuiltInControls, + + #ConditionalAccessSessionControls + [Parameter()] + [System.Boolean] + $ApplicationEnforcedRestrictionsIsEnabled, + + [Parameter()] + [System.Boolean] + $CloudAppSecurityIsEnabled, + + [Parameter()] + [System.String] + $CloudAppSecurityType, + + [Parameter()] + [System.Int32] + $SignInFrequencyValue, + + [Parameter()] + [ValidateSet('Days', 'Hours', '')] + [System.String] + $SignInFrequencyType, + + [Parameter()] + [System.Boolean] + $SignInFrequencyIsEnabled, + + [Parameter()] + [ValidateSet('Always', 'Never', '')] + [System.String] + $PersistentBrowserMode, + + [Parameter()] + [System.Boolean] + $PersistentBrowserIsEnabled, + + #generic + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $GlobalAdminAccount, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + + Write-Verbose -Message "Set-Targetresource: Start processing" + Write-Verbose -Message "Set-Targetresource: Starting telemetry" + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + Write-Verbose -Message "Set-Targetresource: Finished telemetry" + Write-Verbose -Message "Set-Targetresource: Running Get-TargetResource" + $currentPolicy = Get-TargetResource @PSBoundParameters + Write-Verbose -Message "Set-Targetresource: Cleaning up parameters" + $currentParameters = $PSBoundParameters + $currentParameters.Remove("ApplicationId") | Out-Null + $currentParameters.Remove("TenantId") | Out-Null + $currentParameters.Remove("CertificateThumbprint") | Out-Null + $currentParameters.Remove("GlobalAdminAccount") | Out-Null + $currentParameters.Remove("Ensure") | Out-Null + + if ($Ensure -eq 'Present')#create policy attribute objects + { + Write-Verbose -Message "Set-Targetresource: Policy $Displayname Ensure Present" + $NewParameters = @{} + $NewParameters.Add("DisplayName", $DisplayName) + $NewParameters.Add("State", $State) + #create Conditions object + Write-Verbose -Message "Set-Targetresource: create Conditions object" + $conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet + #create and provision Application Condition object + Write-Verbose -Message "Set-Targetresource: create Application Condition object" + $conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationCondition + $conditions.Applications.IncludeApplications = $IncludeApplications + $conditions.Applications.ExcludeApplications = $ExcludeApplications + $conditions.Applications.IncludeUserActions = $IncludeUserActions + #create and provision User Condition object + Write-Verbose -Message "Set-Targetresource: create and provision User Condition object" + $conditions.Users = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessUserCondition + Write-Verbose -Message "Set-Targetresource: process includeusers" + foreach ($includeuser in $IncludeUsers) + { + #translate user UPNs to GUID, except id value is GuestsOrExternalUsers or All + if ($includeuser) + { + if ($includeuser -notin "GuestsOrExternalUsers", "All") + { + $userguid = $null + try + { $userguid = (Get-AzureADUser -ObjectId $includeuser).ObjectId + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + if ($null -eq $userguid) + { + $Message = "Couldn't find user $includeuser , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $conditions.Users.IncludeUsers += $userguid + } + } + else + { + $conditions.Users.IncludeUsers += $includeuser + } + } + } + Write-Verbose -Message "Set-Targetresource: process excludeusers" + foreach ($excludeuser in $ExcludeUsers) + { + #translate user UPNs to GUID, except id value is GuestsOrExternalUsers or All + if ($excludeuser) + { + if ($excludeuser -notin "GuestsOrExternalUsers", "All") + { + $userguid = $null + try + { $userguid = (Get-AzureADUser -ObjectId $excludeuser).ObjectId + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + if ($null -eq $userguid) + { + $Message = "Couldn't find user $excludeuser , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $conditions.Users.ExcludeUsers += $userguid + } + } + else + { + $conditions.Users.ExcludeUsers += $excludeuser + } + } + } + Write-Verbose -Message "Set-Targetresource: process includegroups" + foreach ($includegroup in $IncludeGroups) + { + #translate user Group names to GUID + if ($includegroup) + { + $GroupLookup = $null + try + { $GroupLookup = Get-AzureADGroup -Filter "DisplayName eq '$includegroup'" + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + Write-Verbose -Message $_ + } + if ($GroupLookup.Length -gt 1) + { + $Message = "Duplicate group found with displayname $includegroup , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + elseif ($null -eq $GroupLookup) + { + $Message = "Couldn't find group $includegroup , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + } + else + { + Write-Verbose -Message "adding group to includegroups" + $conditions.Users.IncludeGroups += $GroupLookup.ObjectId + } + } + } + Write-Verbose -Message "Set-Targetresource: process excludegroups" + foreach ($ExcludeGroup in $ExcludeGroups) + { + #translate user Group names to GUID + if ($ExcludeGroup) + { + $GroupLookup = $null + try + { $GroupLookup = Get-AzureADGroup -Filter "DisplayName eq '$ExcludeGroup'" + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + Write-Verbose -Message $_ + } + if ($GroupLookup.Length -gt 1) + { + $Message = "Duplicate group found with displayname $ExcludeGroup , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + } + elseif ($null -eq $GroupLookup) + { + $Message = "Couldn't find group $ExcludeGroup , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + } + else + { + Write-Verbose -Message "adding group to ExcludeGroups" + $conditions.Users.ExcludeGroups += $GroupLookup.ObjectId + } + } + } + Write-Verbose -Message "Set-Targetresource: process includeroles" + if ($IncludeRoles) + { + #translate role names to template guid if defined + $rolelookup = @{} + foreach ($role in Get-AzureADDirectoryRoleTemplate) + { $rolelookup[$role.DisplayName] = $role.ObjectId + } + foreach ($IncludeRole in $IncludeRoles) + { + if ($IncludeRole) + { + if ($null -eq $rolelookup[$IncludeRole]) + { + $Message = "Couldn't find role $IncludeRole , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $conditions.Users.IncludeRoles += $rolelookup[$IncludeRole] + } + } + } + } + Write-Verbose -Message "Set-Targetresource: process excluderoles" + if ($ExcludeRoles) + { + #translate role names to template guid if defined + $rolelookup = @{} + foreach ($role in Get-AzureADDirectoryRoleTemplate) + { $rolelookup[$role.DisplayName] = $role.ObjectId + } + foreach ($ExcludeRole in $ExcludeRoles) + { + if ($ExcludeRole) + { + if ($null -eq $rolelookup[$ExcludeRole]) + { + $Message = "Couldn't find role $ExcludeRole , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $conditions.Users.ExcludeRoles += $rolelookup[$ExcludeRole] + } + + } + } + } + Write-Verbose -Message "Set-Targetresource: process includeplatforms" + if ($IncludePlatforms -or $ExcludePlatforms) + { + #create and provision Platform condition object if used + $conditions.Platforms = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessPlatformCondition + $conditions.Platforms.IncludePlatforms = $IncludePlatforms + #no translation or conversion needed + $conditions.Platforms.ExcludePlatforms = $ExcludePlatforms + #no translation or conversion needed + } + Write-Verbose -Message "Set-Targetresource: process include and exclude locations" + if ($IncludeLocations -or $ExcludeLocations) + { + Write-Verbose -Message "Set-Targetresource: locations specified" + #create and provision Location condition object if used, translate Location names to guid + $conditions.Locations = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessLocationCondition + $LocationLookup = @{} + foreach ($Location in Get-AzureADMSNamedLocationPolicy) + { + $LocationLookup[$Location.DisplayName] = $Location.Id + } + foreach ($IncludeLocation in $IncludeLocations) + { + if ($IncludeLocation) + { + if ($IncludeLocation -in "All", "AllTrusted") + { + $conditions.Locations.IncludeLocations += $IncludeLocation + } + elseif ($null -eq $LocationLookup[$IncludeLocation]) + { + $Message = "Couldn't find Location $IncludeLocation , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $conditions.Locations.IncludeLocations += $LocationLookup[$IncludeLocation] + } + } + } + foreach ($ExcludeLocation in $ExcludeLocations) + { + if ($ExcludeLocation) + { + if ($ExcludeLocation -eq "All" -or $ExcludeLocation -eq "AllTrusted") + { + $conditions.Locations.ExcludeLocations += $ExcludeLocation + } + elseif ($null -eq $LocationLookup[$ExcludeLocation]) + { + $Message = "Couldn't find Location $ExcludeLocation , couldn't add to policy $DisplayName" + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + } + else + { + $conditions.Locations.ExcludeLocations += $LocationLookup[$ExcludeLocation] + } + } + } + } + + Write-Verbose -Message "Set-Targetresource: process device states" + if ($IncludeDeviceStates -or $ExcludeDeviceStates) + { + #create and provision Device condition object if used + $conditions.Devices = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessDevicesCondition + $conditions.Devices.IncludeDeviceStates = $IncludeDeviceStates + #no translation or conversion needed + $conditions.Devices.ExcludeDeviceStates = $ExcludeDeviceStates + #no translation or conversion needed + } + Write-Verbose -Message "Set-Targetresource: process risk levels and app types" + $Conditions.UserRiskLevels = $UserRiskLevels + #no translation or conversion needed + $Conditions.SignInRiskLevels = $SignInRiskLevels + #no translation or conversion needed + $Conditions.ClientAppTypes = $ClientAppTypes + #no translation or conversion needed + Write-Verbose -Message "Set-Targetresource: Adding processed conditions" + #add all conditions to the parameter list + $NewParameters.Add("Conditions", $Conditions) + #create and provision Grant Control object + Write-Verbose -Message "Set-Targetresource: create and provision Grant Control object" + $GrantControls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls + $GrantControls._Operator = $GrantControlOperator + $GrantControls.BuiltInControls = $BuiltInControls + #no translation or conversion needed + Write-Verbose -Message "Set-Targetresource: Adding processed grant controls" + $NewParameters.Add("GrantControls", $GrantControls) + #add GrantControls to the parameter list + Write-Verbose -Message "Set-Targetresource: process session controls" + if ($ApplicationEnforcedRestrictionsIsEnabled -or $CloudAppSecurityIsEnabled -or $SignInFrequencyIsEnabled -or $PersistentBrowserIsEnabled) + { + #create and provision Session Control object if used + Write-Verbose -Message "Set-Targetresource: Create and provision Session Control object" + $sessioncontrols = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessSessionControls + if ($ApplicationEnforcedRestrictionsIsEnabled) + { + #create and provision ApplicationEnforcedRestrictions object if used + $sessioncontrols.ApplicationEnforcedRestrictions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationEnforcedRestrictions + $sessioncontrols.ApplicationEnforcedRestrictions.IsEnabled = $true + } + if ($CloudAppSecurityIsEnabled) + { + #create and provision CloudAppSecurity object if used + $sessioncontrols.CloudAppSecurity = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessCloudAppSecurity + $sessioncontrols.CloudAppSecurity.IsEnabled = $true + $sessioncontrols.CloudAppSecurity.CloudAppSecurityType = $CloudAppSecurityType + } + if ($SignInFrequencyIsEnabled) + { + #create and provision SignInFrequency object if used + $sessioncontrols.SignInFrequency = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessSignInFrequency + $sessioncontrols.SignInFrequency.IsEnabled = $true + $sessioncontrols.SignInFrequency.Type = $SignInFrequencyType + $sessioncontrols.SignInFrequency.Value = $SignInFrequencyValue + } + if ($PersistentBrowserIsEnabled) + { + #create and provision PersistentBrowser object if used + $sessioncontrols.PersistentBrowser = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessPersistentBrowser + $sessioncontrols.PersistentBrowser.IsEnabled = $true + $sessioncontrols.PersistentBrowser.Mode = $PersistentBrowserMode + } + $NewParameters.Add("SessionControls", $sessioncontrols) + #add SessionControls to the parameter list + } + + } + if ($Ensure -eq 'Present' -and $currentPolicy.Ensure -eq 'Present') + { + Write-Verbose -Message "Set-Targetresource: Change policy $DisplayName" + $NewParameters.Add("PolicyId", $currentPolicy.Id) + try + { + Set-AzureADMSConditionalAccessPolicy @NewParameters + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + Write-Verbose -Message "Set-Targetresource: Failed change policy $DisplayName" + Write-Verbose -Message $_ + } + } + elseif ($Ensure -eq 'Present' -and $currentPolicy.Ensure -eq 'Absent') + { + Write-Verbose -Message "Set-Targetresource: create policy $DisplayName" + try + { + New-AzureADMSConditionalAccessPolicy @NewParameters + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + Write-Verbose -Message "Set-Targetresource: Failed creating policy" + Write-Verbose -Message $_ + } + } + elseif ($Ensure -eq 'Absent' -and $currentPolicy.Ensure -eq 'Present') + { + Write-Verbose -Message "Set-Targetresource: delete policy $DisplayName" + try + { + Remove-AzureADMSConditionalAccessPolicy -PolicyId $currentPolicy.ID + } + catch + { + $Message = $_ + try + { + Write-Verbose -Message $Message + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + + Write-Verbose -Message "Set-Targetresource: Failed deleting policy $DisplayName" + Write-Verbose -Message $_ + } + } + Write-Verbose -Message "Set-Targetresource: finished processing Policy $Displayname" +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + [ValidateSet('disabled', 'enabled', 'enabledForReportingButNotEnforced')] + $State, + + #ConditionalAccessApplicationCondition + [Parameter()] + [System.String[]] + $IncludeApplications, + + [Parameter()] + [System.String[]] + $ExcludeApplications, + + [Parameter()] + [System.String[]] + $IncludeUserActions, + + #ConditionalAccessUserCondition + [Parameter()] + [System.String[]] + $IncludeUsers, + + [Parameter()] + [System.String[]] + $ExcludeUsers, + + [Parameter()] + [System.String[]] + $IncludeGroups, + + [Parameter()] + [System.String[]] + $ExcludeGroups, + + [Parameter()] + [System.String[]] + $IncludeRoles, + + [Parameter()] + [System.String[]] + $ExcludeRoles, + + #ConditionalAccessPlatformCondition + [Parameter()] + [System.String[]] + $IncludePlatforms, + + [Parameter()] + [System.String[]] + $ExcludePlatforms, + + #ConditionalAccessLocationCondition + [Parameter()] + [System.String[]] + $IncludeLocations, + + [Parameter()] + [System.String[]] + $ExcludeLocations, + + #ConditionalAccessDevicesCondition + [Parameter()] + [System.String[]] + $IncludeDeviceStates, + + [Parameter()] + [System.String[]] + $ExcludeDeviceStates, + + #Further conditions + [Parameter()] + [System.String[]] + $UserRiskLevels, + + [Parameter()] + [System.String[]] + $SignInRiskLevels, + + [Parameter()] + [System.String[]] + $ClientAppTypes, + + #ConditionalAccessGrantControls + [Parameter()] + [ValidateSet('AND', 'OR')] + [System.String] + $GrantControlOperator, + + [Parameter()] + [System.String[]] + $BuiltInControls, + + #ConditionalAccessSessionControls + [Parameter()] + [System.Boolean] + $ApplicationEnforcedRestrictionsIsEnabled, + + [Parameter()] + [System.Boolean] + $CloudAppSecurityIsEnabled, + + [Parameter()] + [System.String] + $CloudAppSecurityType, + + [Parameter()] + [System.Int32] + $SignInFrequencyValue, + + [Parameter()] + [ValidateSet('Days', 'Hours', '')] + [System.String] + $SignInFrequencyType, + + [Parameter()] + [System.Boolean] + $SignInFrequencyIsEnabled, + + [Parameter()] + [ValidateSet('Always', 'Never', '')] + [System.String] + $PersistentBrowserMode, + + [Parameter()] + [System.Boolean] + $PersistentBrowserIsEnabled, + + #generic + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $GlobalAdminAccount, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + + Write-Verbose -Message "Testing configuration of AzureAD CA Policies" + + $CurrentValues = Get-TargetResource @PSBoundParameters + + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" + + $ValuesToCheck = $PSBoundParameters + $ValuesToCheck.Remove('GlobalAdminAccount') | Out-Null + $ValuesToCheck.Remove('Id') | Out-Null + + $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` + -Source $($MyInvocation.MyCommand.Source) ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck $ValuesToCheck.Keys + + Write-Verbose -Message "Test-TargetResource returned $TestResult" + + return $TestResult +} + +function Export-TargetResource +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.Management.Automation.PSCredential] + $GlobalAdminAccount, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $ConnectionMode = New-M365DSCConnection -Platform 'AzureAD' -InboundParameters $PSBoundParameters + try + { + [array] $Policies = Get-AzureADMSConditionalAccessPolicy + $i = 1 + $dscContent = '' + Write-Host "`r`n" -NoNewline + foreach ($Policy in $Policies) + { + Write-Host " |---[$i/$($Policies.Count)] $($Policy.DisplayName)" -NoNewline + $Params = @{ + GlobalAdminAccount = $GlobalAdminAccount + DisplayName = $Policy.DisplayName + Id = $Policy.Id + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + } + $Results = Get-TargetResource @Params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + $dscContent += Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -GlobalAdminAccount $GlobalAdminAccount + Write-Host $Global:M365DSCEmojiGreenCheckMark + $i++ + } + return $dscContent + } + catch + { + try + { + Write-Verbose -Message $_ + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + return "" + } +} + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.schema.mof new file mode 100644 index 0000000000..6ecfec08ae --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.schema.mof @@ -0,0 +1,40 @@ +[ClassVersion("1.0.0.0"), FriendlyName("AADConditionalAccessPolicy")] +class MSFT_AADConditionalAccessPolicy : OMI_BaseResource +{ + [Key, Description("DisplayName of the AAD CA Policy")] String DisplayName; + [Write, Description("Specifies the GUID for the Policy.")] String Id; + [Write, Description("Specifies the State of the Policy."), ValueMap{"disabled","enabled","enabledForReportingButNotEnforced"}, Values{"disabled","enabled","enabledForReportingButNotEnforced"}] String State; + [Write, Description("Cloud Apps in scope of the Policy.")] String IncludeApplications[]; + [Write, Description("Cloud Apps out of scope of the Policy.")] String ExcludeApplications[]; + [Write, Description("User Actions in scope of the Policy.")] String IncludeUserActions[]; + [Write, Description("Users in scope of the Policy.")] String IncludeUsers[]; + [Write, Description("Users out of scope of the Policy.")] String ExcludeUsers[]; + [Write, Description("Groups in scope of the Policy.")] String IncludeGroups[]; + [Write, Description("Groups out of scope of the Policy.")] String ExcludeGroups[]; + [Write, Description("AAD Admin Roles in scope of the Policy.")] String IncludeRoles[]; + [Write, Description("AAD Admin Roles out of scope of the Policy.")] String ExcludeRoles[]; + [Write, Description("Client Device Platforms in scope of the Policy.")] String IncludePlatforms[]; + [Write, Description("Client Device Platforms out of scope of the Policy.")] String ExcludePlatforms[]; + [Write, Description("AAD Named Locations in scope of the Policy.")] String IncludeLocations[]; + [Write, Description("AAD Named Locations out of scope of the Policy.")] String ExcludeLocations[]; + [Write, Description("Client Device Compliance states in scope of the Policy.")] String IncludeDeviceStates[]; + [Write, Description("Client Device Compliance states out of scope of the Policy.")] String ExcludeDeviceStates[]; + [Write, Description("AAD Identity Protection User Risk Levels in scope of the Policy.")] String UserRiskLevels[]; + [Write, Description("AAD Identity Protection Sign-in Risk Levels in scope of the Policy.")] String SignInRiskLevels[]; + [Write, Description("Client App types in scope of the Policy.")] String ClientAppTypes[]; + [Write, Description("Operator to be used for Grant Controls."), ValueMap{"AND","OR"}, Values{"AND","OR"}] String GrantControlOperator; + [Write, Description("List of built-in Grant Controls to be applied by the Policy.")] String BuiltInControls[]; + [Write, Description("Specifies, whether Application Enforced Restrictions are enabled in the Policy.")] Boolean ApplicationEnforcedRestrictionsIsEnabled; + [Write, Description("Specifies, whether Cloud App Security is enforced by the Policy.")] Boolean CloudAppSecurityIsEnabled; + [Write, Description("Specifies, what Cloud App Security control is enforced by the Policy.")] String CloudAppSecurityType; + [Write, Description("Sign in frequency time in the given unit to be enforced by the policy.")] UInt32 SignInFrequencyValue; + [Write, Description("Sign in frequency unit (days/hours) to be interpreted by the policy."), ValueMap{"Days","Hours",""}, Values{"Days","Hours",""}] String SignInFrequencyType; + [Write, Description("Specifies, whether sign-in frequency is enforced by the Policy.")] Boolean SignInFrequencyIsEnabled; + [Write, Description("Specifies, whether Browser Persistence is controlled by the Policy.")] Boolean PersistentBrowserIsEnabled; + [Write, Description("Specifies, what Browser Persistence control is enforced by the Policy."), ValueMap{"Always","Never",""}, Values{"Always","Never",""}] String PersistentBrowserMode; + [Write, Description("Specify if the Azure AD CA Policy should exist or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("Credentials of the Azure Active Directory Admin"), EmbeddedInstance("MSFT_Credential")] String GlobalAdminAccount; + [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; + [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; + [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/Readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/Readme.md new file mode 100644 index 0000000000..454ffaac1f --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/Readme.md @@ -0,0 +1,34 @@ +# AADConditionalAccessPolicy + +## Description + +This resource configures an Azure Active Directory Conditional Access Policy. + +## Azure AD Permissions + +To authenticate via Azure Active Directory, this resource required the following Application permissions: + +* **Automate** + * Application.Read.All + * Group.Read.All + * Directory.Read.All + * Policy.Read.All + * Policy.Read.ConditionalAccess + * Policy.ReadWrite.ConditionalAccess + * RoleManagement.Read.All + * RoleManagement.Read.Directory + * User.Read.All + +* **Export** + * Application.Read.All + * Group.Read.All + * Directory.Read.All + * Policy.Read.All + * Policy.Read.ConditionalAccess + * RoleManagement.Read.All + * RoleManagement.Read.Directory + * User.Read.All + +NOTE: All permisions listed above require admin consent. + +Additionally Global Reader Role needs to be assigned, as long as AAD PowerShell is not fully converged to use GRAPH API diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_EXOSafeLinksRule/MSFT_EXOSafeLinksRule.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_EXOSafeLinksRule/MSFT_EXOSafeLinksRule.psm1 index a17c2e0eb1..5cfe424eed 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_EXOSafeLinksRule/MSFT_EXOSafeLinksRule.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_EXOSafeLinksRule/MSFT_EXOSafeLinksRule.psm1 @@ -499,7 +499,7 @@ function Export-TargetResource } catch { - Wtry + try { Write-Verbose -Message $_ $tenantIdValue = "" diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_O365User/MSFT_O365User.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_O365User/MSFT_O365User.psm1 index 4a00516481..c1eeea340f 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_O365User/MSFT_O365User.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_O365User/MSFT_O365User.psm1 @@ -370,124 +370,132 @@ function Set-TargetResource -InboundParameters $PSBoundParameters $user = Get-TargetResource @PSBoundParameters - $PasswordPolicies = $null - if ($PasswordNeverExpires) + if ($user.Ensure -eq 'Present' -and $Ensure -eq 'Absent') { - $PasswordPolicies = 'DisablePasswordExpiration' + Write-Verbose -Message "Removing User {$UserPrincipalName}" + Remove-AzureADUser -ObjectId $UserPrincipalName } else { - $PasswordPolicies = "None" - } - $CreationParams = @{ - City = $City - Country = $Country - Department = $Department - DisplayName = $DisplayName - FacsimileTelephoneNumber = $Fax - GivenName = $FirstName - JobTitle = $Title - Mobile = $MobilePhone - PasswordPolicies = $PasswordPolicies - PhysicalDeliveryOfficeName = $Office - PostalCode = $PostalCode - PreferredLanguage = $PreferredLanguage - State = $State - StreetAddress = $StreetAddress - Surname = $LastName - TelephoneNumber = $PhoneNumber - UsageLocation = $UsageLocation - UserPrincipalName = $UserPrincipalName - UserType = $UserType - } - $CreationParams = Remove-NullEntriesFromHashtable -Hash $CreationParams - - $licenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses - - foreach ($licenseSkuPart in $LicenseAssignment) - { - Write-Verbose -Message "Adding License {$licenseSkuPart} to the Queue" - $license = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense - $license.SkuId = (Get-AzureADSubscribedSku | Where-Object -Property SkuPartNumber -Value $licenseSkuPart -EQ).SkuID + $PasswordPolicies = $null + if ($PasswordNeverExpires) + { + $PasswordPolicies = 'DisablePasswordExpiration' + } + else + { + $PasswordPolicies = "None" + } + $CreationParams = @{ + City = $City + Country = $Country + Department = $Department + DisplayName = $DisplayName + FacsimileTelephoneNumber = $Fax + GivenName = $FirstName + JobTitle = $Title + Mobile = $MobilePhone + PasswordPolicies = $PasswordPolicies + PhysicalDeliveryOfficeName = $Office + PostalCode = $PostalCode + PreferredLanguage = $PreferredLanguage + State = $State + StreetAddress = $StreetAddress + Surname = $LastName + TelephoneNumber = $PhoneNumber + UsageLocation = $UsageLocation + UserPrincipalName = $UserPrincipalName + UserType = $UserType + } + $CreationParams = Remove-NullEntriesFromHashtable -Hash $CreationParams - # Set the Office license as the license we want to add in the $licenses object - $licenses.AddLicenses += $license - } + $licenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses - foreach ($currentLicense in $user.LicenseAssignment) - { - if (-not $LicenseAssignment.Contains($currentLicense)) + foreach ($licenseSkuPart in $LicenseAssignment) { - Write-Verbose -Message "Removing {$currentLicense} from user {$UserPrincipalName}" - $license = (Get-AzureADSubscribedSku | Where-Object -Property SkuPartNumber -Value $currentLicense -EQ).SkuID - $licenses.RemoveLicenses += $license - } - } + Write-Verbose -Message "Adding License {$licenseSkuPart} to the Queue" + $license = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense + $license.SkuId = (Get-AzureADSubscribedSku | Where-Object -Property SkuPartNumber -Value $licenseSkuPart -EQ).SkuID - if ($user.UserPrincipalName) - { + # Set the Office license as the license we want to add in the $licenses object + $licenses.AddLicenses += $license + } - if ($null -ne $licenses -and ` - ($licenses.AddLicenses.Length -gt 0 -or $licenses.RemoveLicenses.Length -gt 0)) + foreach ($currentLicense in $user.LicenseAssignment) { - Write-Verbose -Message "Updating License Assignment {$($licenses[0] | Out-String)}" - try - { - Write-Verbose -Message "Assigning $($licenses.Count) licences to existing user" - Set-AzureADUserLicense -ObjectId $UserPrincipalName ` - -AssignedLicenses $licenses ` - -ErrorAction SilentlyContinue - } - catch + if (-not $LicenseAssignment.Contains($currentLicense)) { - $Message = "License {$($LicenseAssignment)} doesn't exist in tenant." - Write-Verbose $Message - New-M365DSCLogEntry -Error $_ -Message $Message -Source $MyInvocation.MyCommand.ModuleName + Write-Verbose -Message "Removing {$currentLicense} from user {$UserPrincipalName}" + $license = (Get-AzureADSubscribedSku | Where-Object -Property SkuPartNumber -Value $currentLicense -EQ).SkuID + $licenses.RemoveLicenses += $license } } - Write-Verbose -Message "Updating Office 365 User $UserPrincipalName Information" - $CreationParams.Add("ObjectID", $UserPrincipalName) - $user = Set-AzureADUser @CreationParams - } - else - { - Write-Verbose -Message "Creating Office 365 User $UserPrincipalName" - $CreationParams.Add("AccountEnabled", $true) - $PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile - $PasswordProfile.Password = 'TempP@ss' - $CreationParams.Add("PasswordProfile", $PasswordProfile) - $CreationParams.Add("MailNickName", $UserPrincipalName.Split('@')[0]) - $user = New-AzureADUser @CreationParams - Set-AzureADUserPassword -ObjectId $UserPrincipalName -Password $Password.Password | Out-Null - - try + if ($user.UserPrincipalName) { + if ($null -ne $licenses -and ` - ($licenses.AddLicenses.Length -gt 0)) + ($licenses.AddLicenses.Length -gt 0 -or $licenses.RemoveLicenses.Length -gt 0)) { - Write-Verbose -Message "Assigning $($licenses.Count) licences to new user" - Set-AzureADUserLicense -ObjectId $UserPrincipalName ` - -AssignedLicenses $licenses ` - -ErrorAction SilentlyContinue + Write-Verbose -Message "Updating License Assignment {$($licenses[0] | Out-String)}" + try + { + Write-Verbose -Message "Assigning $($licenses.Count) licences to existing user" + Set-AzureADUserLicense -ObjectId $UserPrincipalName ` + -AssignedLicenses $licenses ` + -ErrorAction SilentlyContinue + } + catch + { + $Message = "License {$($LicenseAssignment)} doesn't exist in tenant." + Write-Verbose $Message + New-M365DSCLogEntry -Error $_ -Message $Message -Source $MyInvocation.MyCommand.ModuleName + } } + + Write-Verbose -Message "Updating Office 365 User $UserPrincipalName Information" + $CreationParams.Add("ObjectID", $UserPrincipalName) + $user = Set-AzureADUser @CreationParams } - catch + else { - $Message = "Could not assign license {$($licenses.AddLicenses)} to user {$UserPrincipalName}" - $tenantIdValue = "" - if (-not [System.String]::IsNullOrEmpty($TenantId)) + Write-Verbose -Message "Creating Office 365 User $UserPrincipalName" + $CreationParams.Add("AccountEnabled", $true) + $PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile + $PasswordProfile.Password = 'TempP@ss' + $CreationParams.Add("PasswordProfile", $PasswordProfile) + $CreationParams.Add("MailNickName", $UserPrincipalName.Split('@')[0]) + $user = New-AzureADUser @CreationParams + Set-AzureADUserPassword -ObjectId $UserPrincipalName -Password $Password.Password | Out-Null + + try { - $tenantIdValue = $TenantId + if ($null -ne $licenses -and ` + ($licenses.AddLicenses.Length -gt 0)) + { + Write-Verbose -Message "Assigning $($licenses.Count) licences to new user" + Set-AzureADUserLicense -ObjectId $UserPrincipalName ` + -AssignedLicenses $licenses ` + -ErrorAction SilentlyContinue + } } - elseif ($null -ne $GlobalAdminAccount) + catch { - $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + $Message = "Could not assign license {$($licenses.AddLicenses)} to user {$UserPrincipalName}" + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + New-M365DSCLogEntry -Error $_ -Message $Message ` + -Source $MyInvocation.MyCommand.ModuleName ` + -TenantId $tenantIdValue + throw $Message } - New-M365DSCLogEntry -Error $_ -Message $Message ` - -Source $MyInvocation.MyCommand.ModuleName ` - -TenantId $tenantIdValue - throw $Message } } } diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannel/MSFT_TeamsChannel.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannel/MSFT_TeamsChannel.psm1 index ffc885c61f..8ff6b1ed59 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannel/MSFT_TeamsChannel.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannel/MSFT_TeamsChannel.psm1 @@ -196,13 +196,13 @@ function Set-TargetResource } Write-Verbose -Message "Retrieve team GroupId: $($team.GroupId)" - $CurrentParameters.Remove("TeamName") + $CurrentParameters.Remove("TeamName") | Out-Null $CurrentParameters.Add("GroupId", $team.GroupId) - $CurrentParameters.Remove("GlobalAdminAccount") - $CurrentParameters.Remove("ApplicationId") - $CurrentParameters.Remove("TenantId") - $CurrentParameters.Remove("CertificateThumbprint") - $CurrentParameters.Remove("Ensure") + $CurrentParameters.Remove("GlobalAdminAccount") | Out-Null + $CurrentParameters.Remove("ApplicationId") | Out-Null + $CurrentParameters.Remove("TenantId") | Out-Null + $CurrentParameters.Remove("CertificateThumbprint") | Out-Null + $CurrentParameters.Remove("Ensure") | Out-Null if ($Ensure -eq "Present") { diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/MSFT_TeamsChannelTab.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/MSFT_TeamsChannelTab.psm1 new file mode 100644 index 0000000000..1b1da34892 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/MSFT_TeamsChannelTab.psm1 @@ -0,0 +1,651 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $DisplayName, + + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $TeamName, + + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $ChannelName, + + [Parameter()] + [System.String] + $TeamId, + + [Parameter()] + [System.String] + $TeamsApp, + + [Parameter()] + [System.UInt32] + $SortOrderIndex, + + [Parameter()] + [System.String] + $WebSiteUrl, + + [Parameter()] + [System.String] + $EntityId, + + [Parameter()] + [System.String] + $ContentUrl, + + [Parameter()] + [System.String] + $RemoveUrl, + + [Parameter()] + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = "Present", + + [Parameter(Mandatory = $true)] + [System.String] + $ApplicationId, + + [Parameter(Mandatory = $true)] + [System.String] + $TenantId, + + [Parameter(Mandatory = $true)] + [System.String] + $CertificateThumbprint + ) + Write-Verbose -Message "Getting configuration of Tab $DisplayName" + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $nullReturn = $PSBoundParameters + $nullReturn.Ensure = "Absent" + + $ConnectionMode = New-M365DSCConnection -Platform 'MicrosoftTeams' ` + -InboundParameters $PSBoundParameters + + try + { + # Get the Team ID + try + { + if ([System.String]::IsNullOrEmpty($TeamId)) + { + Write-Verbose -Message "Getting team by Name {$TeamName}" + [array]$teamInstance = Get-Team | Where-Object -FilterScript { $_.DisplayName -eq $TeamName } + if ($teamInstance.Length -gt 1) + { + throw "Multiple Teams with name {$TeamName} were found. Please specify TeamId in your configuration instead." + } + } + else + { + Write-Verbose -Message "Getting team by Id {$TeamId}" + $teamInstance = Get-Team -GroupId $TeamId -ErrorAction Stop + } + } + catch + { + Write-Verbose -Message $_ + Write-Verbose "The specified Service Principal doesn't have access to read Group information. Permission Required: Group.Read.All & Team.ReadBasic.All" + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId + } + + if ($null -eq $teamInstance) + { + $Message = "Team {$TeamName} was not found." + Add-M365DSCEvent -Message $Message -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantID + throw $Message + } + + $nullReturn.TeamId = $teamInstance.GroupId + + $ConnectionMode = New-M365DSCConnection -Platform 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + # Get the Channel ID + Write-Verbose -Message "Getting Channels for Team {$TeamName} with ID {$($teamInstance.GroupId)}" + $channelInstance = Get-MgTeamChannel -TeamId $teamInstance.GroupId | Where-Object -FilterScript { $_.DisplayName -eq $ChannelName } + + if ($null -eq $channelInstance) + { + Add-M365DSCEvent -Message "Could not find Channels for Team {$($teamInstance.GroupId)}" -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantID + throw "Channel {$ChannelName} was not found." + } + + # Get the Channel Tab + Write-Verbose -Message "Getting Tabs for Channel {$ChannelName}" + [array]$tabInstance = Get-M365DSCTeamChannelTab -TeamId $teamInstance.GroupId ` + -ChannelId $channelInstance.Id ` + -DisplayName $DisplayName + + if ($tabInstance.Length -gt 1) + { + throw "More than one instance of a tab with name {$DisplayName} was found." + } + + if ($null -eq $tabInstance) + { + $nullResult = $PSBoundParameters + $nullResult.Ensure = "Absent" + return $nullResult + } + + return @{ + DisplayName = $tabInstance.DisplayName + TeamName = $TeamName + TeamId = $teamInstance.GroupId + ChannelName = $channelInstance.DisplayName + SortOrderIndex = $tabInstance.SortOrderIndex + WebSiteUrl = $tabInstance.configuration.websiteUrl + ContentUrl = $tabInstance.configuration.contentUrl + RemoveUrl = $tabInstance.configuration.removeUrl + EntityId = $tabInstance.configuration.entityId + TeamsApp = $tabInstance.teamsApp.id + ApplicationId = $ApplicationId + TenantId = $TenantID + CertificateThumbprint = $CertificateThumbprint + Ensure = "Present" + } + } + catch + { + try + { + Write-Verbose -Message $_ + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $GlobalAdminAccount) + { + $tenantIdValue = $GlobalAdminAccount.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + throw $_ + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $DisplayName, + + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $TeamName, + + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $ChannelName, + + [Parameter()] + [System.String] + $TeamId, + + [Parameter()] + [System.String] + $TeamsApp, + + [Parameter()] + [System.UInt32] + $SortOrderIndex, + + [Parameter()] + [System.String] + $WebSiteUrl, + + [Parameter()] + [System.String] + $EntityId, + + [Parameter()] + [System.String] + $ContentUrl, + + [Parameter()] + [System.String] + $RemoveUrl, + + [Parameter()] + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = "Present", + + [Parameter(Mandatory = $true)] + [System.String] + $ApplicationId, + + [Parameter(Mandatory = $true)] + [System.String] + $TenantId, + + [Parameter(Mandatory = $true)] + [System.String] + $CertificateThumbprint + ) + + Write-Verbose -Message "Setting configuration of Team $DisplayName" + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $ConnectionMode = New-M365DSCConnection -Platform 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + $tab = Get-TargetResource @PSBoundParameters + + $CurrentParameters = $PSBoundParameters + $CurrentParameters.Remove("Ensure") | Out-Null + $CurrentParameters.Remove("ApplicationId") | Out-Null + $CurrentParameters.Remove("TenantId") | Out-Null + $CurrentParameters.Remove("CertificateThumbprint") | Out-Null + + Write-Verbose -Message "Retrieving Team Channel {$ChannelName} from Team {$($tab.TeamId)}" + $ChannelInstance = Get-MgTeamChannel -TeamId $tab.TeamId ` + -Filter "DisplayName eq '$ChannelName'" + + if ($Ensure -eq "Present" -and ($tab.Ensure -eq "Present")) + { + Write-Verbose -Message "Retrieving Tab {$DisplayName} from Channel {$($ChannelInstance.Id))} from Team {$($tab.TeamId)}" + $tabInstance = Get-M365DSCTeamChannelTab -TeamId $tab.TeamId ` + -ChannelId $ChannelInstance.Id ` + -DisplayName $DisplayName + + Set-M365DSCTeamsChannelTab -Parameters $CurrentParameters ` + -TabId $tabInstance.Id | Out-Null + } + elseif ($Ensure -eq "Present" -and ($tab.Ensure -eq "Absent")) + { + Write-Verbose -Message "Creating new tab {$DisplayName}" + Write-Verbose -Message "Params: $($CurrentParameters | Out-String)" + $CurrentParameters.Add("TeamsId", $tab.TeamId) + $CurrentParameters.Add("ChannelId", $ChannelInstance.Id) + $CurrentParameters.Remove("TeamName") | Out-Null + $CurrentParameters.Remove("ChannelName") | Out-Null + New-M365DSCTeamsChannelTab -Parameters $CurrentParameters + } + elseif ($Ensure -eq "Absent" -and ($tab.Ensure -eq "Present")) + { + Write-Verbose -Message "Retrieving Tab {$DisplayName} from Channel {$($ChannelInstance.Id))} from Team {$($tab.TeamId)}" + $tabInstance = Get-M365DSCTeamChannelTab -TeamId $tab.TeamId ` + -ChannelId $ChannelInstance.Id ` + -DisplayName $DisplayName + Write-Verbose -Message "Removing existing tab {$DisplayName}" + $RemoveParams = @{ + ChannelId = $ChannelInstance.Id + TeamId = $tab.TeamId + TeamsTabId = $tabInstance.Id + } + Remove-MgTeamChannelTab @RemoveParams | Out-Null + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $DisplayName, + + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $TeamName, + + [Parameter(Mandatory = $true)] + [System.String] + [ValidateLength(1, 256)] + $ChannelName, + + [Parameter()] + [System.String] + $TeamId, + + [Parameter()] + [System.String] + $TeamsApp, + + [Parameter()] + [System.UInt32] + $SortOrderIndex, + + [Parameter()] + [System.String] + $WebSiteUrl, + + [Parameter()] + [System.String] + $EntityId, + + [Parameter()] + [System.String] + $ContentUrl, + + [Parameter()] + [System.String] + $RemoveUrl, + + [Parameter()] + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = "Present", + + [Parameter(Mandatory = $true)] + [System.String] + $ApplicationId, + + [Parameter(Mandatory = $true)] + [System.String] + $TenantId, + + [Parameter(Mandatory = $true)] + [System.String] + $CertificateThumbprint + ) + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + + Write-Verbose -Message "Testing configuration of Tab $DisplayName" + Write-Verbose -Message "Parameters: $($PSBoundParameters | Out-String)" + + $CurrentValues = Get-TargetResource @PSBoundParameters + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" + + $ValuesToCheck = $PSBoundParameters + $ValuesToCheck.Remove('TenantId') | Out-Null + $ValuesToCheck.Remove('ApplicationId') | Out-Null + $ValuesToCheck.Remove('CertificateThumbprint') | Out-Null + + $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` + -Source $($MyInvocation.MyCommand.Source) ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck $ValuesToCheck.Keys + + Write-Verbose -Message "Test-TargetResource returned $TestResult" + + return $TestResult +} + +function Export-TargetResource +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [System.Management.Automation.PSCredential] + $GlobalAdminAccount + ) + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace("MSFT_", "") + $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() + $data.Add("Resource", $ResourceName) + $data.Add("Method", $MyInvocation.MyCommand) + $data.Add("Principal", $GlobalAdminAccount.UserName) + $data.Add("TenantId", $TenantId) + Add-M365DSCTelemetryEvent -Data $data + #endregion + + try + { + $ConnectionMode = New-M365DSCConnection -Platform 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + $ConnectionMode = New-M365DSCConnection -Platform 'MicrosoftTeams' ` + -InboundParameters $PSBoundParameters + + [array]$teams = Get-Team + $i = 1 + $dscContent = "" + Write-Host "`r`n" -NoNewline + foreach ($team in $teams) + { + Write-Host " |---[$i/$($teams.Length)] $($team.DisplayName)" + + $channels = $null + try + { + [array]$channels = Get-MgTeamChannel -TeamId $team.GroupId -ErrorAction Stop + } + catch + { + Write-Host " $($Global:M365DSCEmojiRedX) The specified Service Principal doesn't have access to read Channel information. Permission Required: Channel.ReadBasic.All" + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId + } + + $j = 1 + foreach ($channel in $channels) + { + Write-Host " |---[$j/$($channels.Length)] $($channel.DisplayName)" + + $tabs = $null + try + { + [array]$tabs = Get-MgTeamChannelTab -TeamId $team.GroupId ` + -ChannelId $channel.Id -ErrorAction Stop + } + catch + { + Write-Host " $($Global:M365DSCEmojiRedX) The specified Service Principal doesn't have access to read Tab information. Permission Required: TeamsTab.Read.All" + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId + } + + $k = 1 + foreach ($tab in $tabs) + { + Write-Host " |---[$k/$($tabs.Length)] $($tab.DisplayName)" -NoNewline + $params = @{ + TeamName = $team.DisplayName + TeamId = $team.GroupId + ChannelName = $channel.DisplayName + DisplayName = $tab.DisplayName + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + } + $Results = Get-TargetResource @params + + if ($null -ne $Results) + { + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + $dscContent += Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results + } + Write-Host $Global:M365DSCEmojiGreenCheckmark + $k++ + } + $j++ + } + $i++ + } + + return $dscContent + } + catch + { + try + { + Write-Verbose -Message $_ + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId + } + catch + { + Write-Verbose -Message $_ + } + return "" + } +} + +function New-M365DSCTeamsChannelTab +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.Collections.HashTable] + $Parameters + ) + + $jsonContent = @" + { + "displayName": "$($Parameters.DisplayName)", + "teamsApp@odata.bind": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/$($Parameters.TeamsApp)", + "sortOrderIndex": "$($Parameters.SortOrderIndex)", + "configuration": { + "websiteUrl": "$($Parameters.WebSiteUrl)", + "contentUrl": "$($Parameters.ContentUrl)", + "removeURL": "$($Parameters.RemoveUrl)", + "entityId": "$($Parameters.EntityId)" + } + } +"@ + $Url = "https://graph.microsoft.com/beta/teams/$($Parameters.TeamId)/channels/$($Parameters.ChannelId)/tabs" + Write-Verbose -Message "Creating new Teams Tab with JSON payload: `r`n$JSONContent" + Write-Verbose -Message "POST to {$Url}" + Invoke-MgGraphRequest -Method POST ` + -Uri $Url ` + -Body $JSONContent ` + -Headers @{"Content-Type" = "application/json" } | Out-Null +} + +function Set-M365DSCTeamsChannelTab +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.Collections.HashTable] + $Parameters, + + [Parameter(Mandatory = $true)] + [System.String] + $TabID + ) + + $jsonContent = @" + { + "displayName": "$($Parameters.DisplayName)", + "sortOrderIndex": "$($Parameters.SortOrderIndex)", + "configuration": { + "websiteUrl": "$($Parameters.WebSiteUrl)", + "contentUrl": "$($Parameters.ContentUrl)", + "removeURL": "$($Parameters.RemoveUrl)", + "entityId": "$($Parameters.EntityId)" + } + } +"@ + $Url = "https://graph.microsoft.com/beta/teams/$($Parameters.TeamId)/channels/$($Parameters.ChannelId)/tabs/$tabId" + Write-Verbose -Message "Updating Teams Tab with JSON payload: `r`n$JSONContent" + Write-Verbose -Message "PATCH to {$Url}" + Invoke-MgGraphRequest -Method PATCH ` + -Uri $Url ` + -Body $JSONContent ` + -Headers @{"Content-Type" = "application/json" } | Out-Null +} + +function Get-M365DSCTeamChannelTab +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param( + [Parameter(Mandatory = $true)] + [System.String] + $TeamID, + + [Parameter(Mandatory = $true)] + [System.String] + $ChannelId, + + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName + ) + + $Url = "https://graph.microsoft.com/beta/teams/$TeamID/channels/$ChannelId/tabs?Expand=teamsApp&Filter=displayName eq '$($DisplayName.Replace("'","''"))'" + Write-Verbose -Message "Retrieving tab with TeamsID {$TeamID} ChannelID {$ChannelID} DisplayName {$DisplayName}" + Write-Verbose -Message "GET request to {$Url}" + $response = Invoke-MgGraphRequest -Method GET ` + -Uri $Url ` + -Headers @{"Content-Type" = "application/json" } + return $response.value +} + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/MSFT_TeamsChannelTab.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/MSFT_TeamsChannelTab.schema.mof new file mode 100644 index 0000000000..b4c641db36 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/MSFT_TeamsChannelTab.schema.mof @@ -0,0 +1,19 @@ +[ClassVersion("1.0.0.0"), FriendlyName("TeamsChannelTab")] +class MSFT_TeamsChannelTab : OMI_BaseResource +{ + [Key, Description("Display Name of the Channel Tab.")]String DisplayName; + [Required, Description("Display Name of the Team.")]String TeamName; + [Required, Description("Display Name of the Channel.")]String ChannelName; + [Write, Description("Unique Id of the Team of the instance on the source tenant.")]String TeamId; + [Write, Description("Id of the Teams App associated with the custom tab.")]String TeamsApp; + [Write, Description("Index of the sort order for the custom tab.")]UInt32 SortOrderIndex; + [Write, Description("Url of the website linked to the Channel Tab.")]String WebSiteUrl; + [Write, Description("Url of the content linked to the Channel Tab.")]String ContentUrl; + [Write, Description("Url of the location used to remove the app.")]String RemoveUrl; + [Write, Description("Id of the Entity linked to the Channel Tab.")]String EntityId; + [Write, Description("Present ensures the Tab exists, absent ensures it is removed."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; + [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; + [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; +}; + diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/readme.md new file mode 100644 index 0000000000..49d571720e --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsChannelTab/readme.md @@ -0,0 +1,25 @@ +# TeamsChannelTab + +## Description + +This resource configures a new Custom tab in a Channel. + +## Azure AD Permissions + +To authenticate via Azure Active Directory, this resource +required the following Application permissions: + +* **Automate** + * Microsoft.Graph + * Channel.ReadBasic.All + * Group.Read.All + * Team.ReadBasic.All + * TeamsTab.ReadWrite.All +* **Export** + * Microsoft.Graph + * Channel.ReadBasic.All + * Group.Read.All + * Team.ReadBasic.All + * TeamsTab.Read.All + +NOTE: All permisions listed above require admin consent. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTeam/MSFT_TeamsTeam.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTeam/MSFT_TeamsTeam.psm1 index e61141f359..dc74c4e7c5 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTeam/MSFT_TeamsTeam.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTeam/MSFT_TeamsTeam.psm1 @@ -378,14 +378,14 @@ function Set-TargetResource $team = Get-TargetResource @PSBoundParameters $CurrentParameters = $PSBoundParameters - $CurrentParameters.Remove("Ensure") + $CurrentParameters.Remove("Ensure") | Out-Null if ($Ensure -eq "Present" -and ($team.Ensure -eq "Present")) { ## Can't pass Owner parm into set opertaion if ($CurrentParameters.ContainsKey("Owner")) { - $CurrentParameters.Remove("Owner") + $CurrentParameters.Remove("Owner") | Out-Null } if (-not $CurrentParameters.ContainsKey("GroupID")) { @@ -393,13 +393,13 @@ function Set-TargetResource } if ($ConnectionMode -eq 'Credential') { - $CurrentParameters.Remove("GlobalAdminAccount") + $CurrentParameters.Remove("GlobalAdminAccount") | Out-Null } else { - $CurrentParameters.Remove("ApplicationId") - $CurrentParameters.Remove("TenantId") - $CurrentParameters.Remove("CertificateThumbprint") + $CurrentParameters.Remove("ApplicationId") | Out-Null + $CurrentParameters.Remove("TenantId") | Out-Null + $CurrentParameters.Remove("CertificateThumbprint") | Out-Null } Set-Team @CurrentParameters Write-Verbose -Message "Updating team $DisplayName" @@ -409,7 +409,7 @@ function Set-TargetResource ## GroupID not used on New-Team cmdlet if ($CurrentParameters.ContainsKey("GroupID")) { - $CurrentParameters.Remove("GroupID") + $CurrentParameters.Remove("GroupID") | Out-Null } Write-Verbose -Message "Creating team $DisplayName" if ($null -ne $Owner) @@ -452,7 +452,7 @@ function Set-TargetResource } else { - $CurrentParameters.Remove("GlobalAdminAccount") + $CurrentParameters.Remove("GlobalAdminAccount") | Out-Null New-Team @CurrentParameters } } diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADConditionalAccessPolicy/1-ConfigureAADConditionalAccessPolicy.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADConditionalAccessPolicy/1-ConfigureAADConditionalAccessPolicy.ps1 new file mode 100644 index 0000000000..8a7effec50 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADConditionalAccessPolicy/1-ConfigureAADConditionalAccessPolicy.ps1 @@ -0,0 +1,48 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $credsGlobalAdmin + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + AADConditionalAccessPolicy Allin-example + { + GlobalAdminAccount = $credsGlobalAdmin; + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication"); + ClientAppTypes = @("ExchangeActiveSync", "Browser", "MobileAppsAndDesktopClients", "Other"); + CloudAppSecurityIsEnabled = $True; + CloudAppSecurityType = "MonitorOnly"; + DisplayName = "Allin-example"; + Ensure = "Present"; + ExcludeApplications = @("803ee9ca-3f7f-4824-bd6e-0b99d720c35c", "00000012-0000-0000-c000-000000000000", "00000007-0000-0000-c000-000000000000", "Office365"); + ExcludeDeviceStates = @("Compliant", "DomainJoined"); + ExcludeLocations = @("Blocked Countries"); + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS"); + ExcludeRoles = @("Company Administrator", "Application Administrator", "Application Developer", "Cloud Application Administrator", "Cloud Device Administrator"); + ExcludeUsers = @("admin@contoso.com", "AAdmin@contoso.com", "CAAdmin@contoso.com", "AllanD@contoso.com", "AlexW@contoso.com", "GuestsOrExternalUsers"); + GrantControlOperator = "OR"; + IncludeApplications = @("All"); + IncludeDeviceStates = @("All"); + IncludeLocations = @("AllTrusted"); + IncludePlatforms = @("Android", "IOS"); + IncludeUserActions = @(); + IncludeUsers = @("All"); + PersistentBrowserMode = ""; + SignInFrequencyIsEnabled = $True; + SignInFrequencyType = "Hours"; + SignInFrequencyValue = 5; + SignInRiskLevels = @("High", "Medium"); + State = "disabled"; + UserRiskLevels = @("High", "Medium"); + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/TeamsChannelTab/1-CreateNewCustomTab.ps1 b/Modules/Microsoft365DSC/Examples/Resources/TeamsChannelTab/1-CreateNewCustomTab.ps1 new file mode 100644 index 0000000000..bd6dc93cd2 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/TeamsChannelTab/1-CreateNewCustomTab.ps1 @@ -0,0 +1,27 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + TeamsChannelTab MyWebTab + { + ApplicationId = "12345"; + CertificateThumbprint = "ABCDEF1234567890"; + ChannelName = "General"; + ContentUrl = "https://contoso.com"; + DisplayName = "TestTab" + Ensure = "Present" + SortOrderIndex = "10100"; + TeamName = "Contoso Team"; + TeamsApp = "com.microsoft.teamspace.tab.web"; + TenantId = "contoso.onmicrosoft.com"; + WebSiteUrl = "https://contoso.com"; + } + } +} diff --git a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 index 91728b6171..7dbedc084b 100644 --- a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 +++ b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft Corporation # -# Generated on: 2020-12-09 +# Generated on: 2020-12-16 @{ @@ -11,7 +11,7 @@ # RootModule = '' # Version number of this module. - ModuleVersion = '1.20.1209.1' + ModuleVersion = '1.20.1216.1' # Supported PSEditions # CompatiblePSEditions = @() @@ -84,24 +84,12 @@ RequiredVersion = "1.2.0" }, @{ - ModuleName = "Microsoft.PowerApps.Administration.PowerShell" - RequiredVersion = "2.0.99" - }, - @{ - ModuleName = "MicrosoftTeams" - RequiredVersion = "1.1.6" - }, - @{ - ModuleName = "MSCloudLoginAssistant" - RequiredVersion = "1.0.42" - }, - @{ - ModuleName = "MicrosoftTeams" - RequiredVersion = "1.1.6" + ModuleName = "Microsoft.Graph.Teams" + RequiredVersion = "1.2.0" }, @{ - ModuleName = "MSCloudLoginAssistant" - RequiredVersion = "1.0.42" + ModuleName = "Microsoft.PowerApps.Administration.PowerShell" + RequiredVersion = "2.0.99" }, @{ ModuleName = "MicrosoftTeams" @@ -192,26 +180,15 @@ IconUri = 'https://github.com/microsoft/Microsoft365DSC/blob/Dev/Modules/Microsoft365DSC/Dependencies/Images/Logo.png?raw=true' # ReleaseNotes of this module - ReleaseNotes = "* IntuneAppProtectionPolicyiOS - * Initial Release; - * IntuneDeviceCompliancePolicyiOS - * Initial Release; - * IntuneDeviceConfigurationPolicyiOS - * Initial Release; - * IntuneDeviceEnrollmentPlatformRestriction - * Initial Release; - * IntuneDeviceEnrollmentLimitRestriction + ReleaseNotes = "* AADConditionalAccessPolicy * Initial Release; - * TeamsTenantDialPlan - * Performance Improvements: retrieve all Voice Normalization - Rule at once and then iterated through them instead of - retrieving them for every instance. - * DEPENDENCIES - * Upgraded ExchangeOnlineManagement to version 2.0.3; - * Upgraded Microsoft.Graph.Authentication to version 1.2.0; - * Upgraded Microsoft.Graph.Planner to version 1.2.0; - * Upgraded SharePointPnPPowerShellOnline to version - 3.28.2012.0;" + * EXOSafeLinksRule + * Fixed typo in a try/catch clause; + * O365User + * Added support for removing existing users with + Ensure = 'Absent'; + * TeamsChannelTab + * Initial Release;" # Flag to indicate whether the module requires explicit user acceptance for install/update # RequireLicenseAcceptance = $false diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index d2951b55d0..b666707d6d 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -8,7 +8,7 @@ $Global:DefaultComponents = @("SPOApp", "SPOSiteDesign") $Global:FullComponents = @("AADMSGroup", "AADServicePrincipal", "EXOMailboxSettings", "EXOManagementRole", "O365Group", "O365User", ` "PlannerPlan", "PlannerBucket", "PlannerTask", "PPPowerAppsEnvironment", ` "SPOSiteAuditSettings", "SPOSiteGroup", "SPOSite", "SPOUserProfileProperty", "SPOPropertyBag", "TeamsTeam", "TeamsChannel", ` - "TeamsUser") + "TeamsUser", "TeamsChannelTab") #endregion function Format-EXOParams @@ -987,7 +987,8 @@ function New-M365DSCConnection [Parameter(Mandatory = $true)] [ValidateSet("Azure", "AzureAD", "ExchangeOnline", "Intune", ` "SecurityComplianceCenter", "PnP", "PowerPlatforms", ` - "MicrosoftTeams", "SkypeForBusiness", "MicrosoftGraph")] + "MicrosoftTeams", "SkypeForBusiness", "MicrosoftGraph", ` + "MicrosoftGraphBeta")] [System.String] $Platform, @@ -1004,6 +1005,9 @@ function New-M365DSCConnection $SkipModuleReload = $false ) + Write-Verbose -Message "Attempting connection to {$Platform} with:" + Write-Verbose -Message "$($InboundParameters | Out-String)" + if ($SkipModuleReload -eq $true) { $Global:CurrentModeIsExport = $true diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADConditionalAccessPolicy.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADConditionalAccessPolicy.Tests.ps1 new file mode 100644 index 0000000000..afd2e2c5de --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADConditionalAccessPolicy.Tests.ps1 @@ -0,0 +1,612 @@ +[CmdletBinding()] +param( +) +$M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` + -ChildPath "..\..\Unit" ` + -Resolve +$CmdletModule = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath "\Stubs\Microsoft365.psm1" ` + -Resolve) +$GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath "\Stubs\Generic.psm1" ` + -Resolve) +Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` + -ChildPath "\UnitTestHelper.psm1" ` + -Resolve) + +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource "AADConditionalAccessPolicy" -GenericStubModule $GenericStubPath + +Describe -Name $Global:DscHelper.DescribeHeader -Fixture { + InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { + Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope + + BeforeAll { + $secpasswd = ConvertTo-SecureString "Pass@word1)" -AsPlainText -Force + $GlobalAdminAccount = New-Object System.Management.Automation.PSCredential ("tenantadmin", $secpasswd) + + Mock -CommandName Update-M365DSCExportAuthenticationResults -MockWith { + return @{} + } + + Mock -CommandName Get-M365DSCExportContentForResource -MockWith { + + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + + Mock -CommandName New-AzureADMSConditionalAccessPolicy -MockWith { + } + + Mock -CommandName Set-AzureADMSConditionalAccessPolicy -MockWith { + } + + Mock -CommandName Remove-AzureADMSConditionalAccessPolicy -MockWith { + } + } + + # Test contexts + Context -Name "When Conditional Access Policy doesn't exist but should" -Fixture { + BeforeAll { + $testParams = @{ + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + CloudAppSecurityIsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + DisplayName = "Allin" + Ensure = "Present" + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + ExcludeGroups = @("Group 01") + ExcludeLocations = "Contoso LAN" + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + ExcludeRoles = @("Compliance Administrator") + ExcludeUsers = "alexw@contoso.com" + GlobalAdminAccount = $Credsglobaladmin + GrantControlOperator = "AND" + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + IncludeApplications = @("All") + IncludeDeviceStates = @("All") + IncludeGroups = @("Group 01") + IncludeLocations = "AllTrusted" + IncludePlatforms = @("Android", "IOS") + IncludeRoles = @("Compliance Administrator") + IncludeUserActions = @("urn:user:registersecurityinfo") + IncludeUsers = "All" + PersistentBrowserIsEnabled = $True + PersistentBrowserMode = "Always" + SignInFrequencyIsEnabled = $True + SignInFrequencyType = "Days" + SignInFrequencyValue = 5 + SignInRiskLevels = @("High") + State = "disabled" + UserRiskLevels = @("High") + } + + Mock -CommandName Get-AzureADMSConditionalAccessPolicy -MockWith { + return $null + } + Mock -CommandName Get-AzureADUser -MockWith { + return @{ + ObjectId = "76d3c3f6-8269-462b-9385-37435cb33f1e" + UserPrincipalName = "alexw@contoso.com" + } + } + Mock -CommandName Get-AzureADGroup -MockWith { + return @{ + ObjectId = "f1eb1a09-c0c2-4df4-9e69-fee01f00db31" + DisplayName = "Group 01" + } + } + Mock -CommandName Get-AzureADDirectoryRoleTemplate -MockWith { + return @{ + ObjectId = "17315797-102d-40b4-93e0-432062caca18" + DisplayName = "Compliance Administrator" + } + } + Mock -CommandName Get-AzureADMSNamedLocationPolicy -MockWith { + return @{ + Id = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + DisplayName = "Contoso LAN" + } + } + } + + It "Should return absent from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Absent' + } + + It "Should return false from the Test method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should create the policy in the Set method" { + Set-TargetResource @testParams + Should -Invoke -CommandName New-AzureADMSConditionalAccessPolicy -Exactly 1 + } + } + + Context -Name "Policy exists but is not in the Desired State" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationEnforcedRestrictionsIsEnabled = $True + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + CloudAppSecurityIsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + DisplayName = "Allin" + Ensure = "Present" + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + ExcludeGroups = @("Group 01") + ExcludeLocations = "Contoso LAN" + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + ExcludeRoles = @("Compliance Administrator") + ExcludeUsers = "alexw@contoso.com" + GlobalAdminAccount = $Credsglobaladmin + GrantControlOperator = "AND" + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + IncludeApplications = @("All") + IncludeDeviceStates = @("All") + IncludeGroups = @("Group 01") + IncludeLocations = "AllTrusted" + IncludePlatforms = @("Android", "IOS") + IncludeRoles = @("Compliance Administrator") + IncludeUserActions = @("urn:user:registersecurityinfo") + IncludeUsers = "All" + PersistentBrowserIsEnabled = $True + PersistentBrowserMode = "Always" + SignInFrequencyIsEnabled = $True + SignInFrequencyType = "Days" + SignInFrequencyValue = 5 + SignInRiskLevels = @("High") + State = "disabled" + UserRiskLevels = @("High") + } + + Mock -CommandName Get-AzureADMSConditionalAccessPolicy -MockWith { + return @{ + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + DisplayName = "Allin" + State = "enabled" + Conditions = @{ + Applications = @{ + IncludeApplications = @("All") + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + IncludeUserActions = @("urn:user:registersecurityinfo") + } + Users = @{ + IncludeUsers = "All" + ExcludeUsers = "76d3c3f6-8269-462b-9385-37435cb33f1e" + IncludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + ExcludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + IncludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + ExcludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + } + Platforms = @{ + IncludePlatforms = @("Android", "IOS") + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + } + Locations = @{ + IncludeLocations = "AllTrusted" + ExcludeLocations = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + } + Devices = @{ + IncludeDeviceStates = @("All") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + } + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + SignInRiskLevels = @("High") + UserRiskLevels = @("High") + } + GrantControls = @{ + _Operator = "AND" + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + } + SessionControls = @{ + ApplicationEnforcedRestrictions = @{ + IsEnabled = $True + } + CloudAppSecurity = @{ + IsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + } + SignInFrequency = @{ + IsEnabled = $True + Type = "Days" + Value = 5 + } + PersistentBrowser = @{ + IsEnabled = $True + Mode = "Always" + } + } + } + } + Mock -CommandName Get-AzureADUser -MockWith { + return @{ + ObjectId = "76d3c3f6-8269-462b-9385-37435cb33f1e" + UserPrincipalName = "alexw@contoso.com" + } + } + Mock -CommandName Get-AzureADGroup -MockWith { + return @{ + ObjectId = "f1eb1a09-c0c2-4df4-9e69-fee01f00db31" + DisplayName = "Group 01" + } + } + Mock -CommandName Get-AzureADDirectoryRoleTemplate -MockWith { + return @{ + ObjectId = "17315797-102d-40b4-93e0-432062caca18" + DisplayName = "Compliance Administrator" + } + } + Mock -CommandName Get-AzureADMSNamedLocationPolicy -MockWith { + return @{ + Id = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + DisplayName = "Contoso LAN" + } + } + } + + It "Should return Present from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It "Should return false from the Test method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should update the settings from the Set method" { + Set-TargetResource @testParams + Should -Invoke -CommandName Set-AzureADMSConditionalAccessPolicy -Exactly 1 + } + } + + Context -Name "Policy exists and is already in the Desired State" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationEnforcedRestrictionsIsEnabled = $True + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + CloudAppSecurityIsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + DisplayName = "Allin" + Ensure = "Present" + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + ExcludeGroups = @("Group 01") + ExcludeLocations = "Contoso LAN" + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + ExcludeRoles = @("Compliance Administrator") + ExcludeUsers = "alexw@contoso.com" + GlobalAdminAccount = $Credsglobaladmin + GrantControlOperator = "AND" + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + IncludeApplications = @("All") + IncludeDeviceStates = @("All") + IncludeGroups = @("Group 01") + IncludeLocations = "AllTrusted" + IncludePlatforms = @("Android", "IOS") + IncludeRoles = @("Compliance Administrator") + IncludeUserActions = @("urn:user:registersecurityinfo") + IncludeUsers = "All" + PersistentBrowserIsEnabled = $True + PersistentBrowserMode = "Always" + SignInFrequencyIsEnabled = $True + SignInFrequencyType = "Days" + SignInFrequencyValue = 5 + SignInRiskLevels = @("High") + State = "disabled" + UserRiskLevels = @("High") + } + + Mock -CommandName Get-AzureADMSConditionalAccessPolicy -MockWith { + return @{ + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + DisplayName = "Allin" + State = "disabled" + Conditions = @{ + Applications = @{ + IncludeApplications = @("All") + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + IncludeUserActions = @("urn:user:registersecurityinfo") + } + Users = @{ + IncludeUsers = "All" + ExcludeUsers = "76d3c3f6-8269-462b-9385-37435cb33f1e" + IncludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + ExcludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + IncludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + ExcludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + } + Platforms = @{ + IncludePlatforms = @("Android", "IOS") + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + } + Locations = @{ + IncludeLocations = "AllTrusted" + ExcludeLocations = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + } + Devices = @{ + IncludeDeviceStates = @("All") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + } + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + SignInRiskLevels = @("High") + UserRiskLevels = @("High") + } + GrantControls = @{ + _Operator = "AND" + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + } + SessionControls = @{ + ApplicationEnforcedRestrictions = @{ + IsEnabled = $True + } + CloudAppSecurity = @{ + IsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + } + SignInFrequency = @{ + IsEnabled = $True + Type = "Days" + Value = 5 + } + PersistentBrowser = @{ + IsEnabled = $True + Mode = "Always" + } + } + } + } + Mock -CommandName Get-AzureADUser -MockWith { + return @{ + ObjectId = "76d3c3f6-8269-462b-9385-37435cb33f1e" + UserPrincipalName = "alexw@contoso.com" + } + } + Mock -CommandName Get-AzureADGroup -MockWith { + return @{ + ObjectId = "f1eb1a09-c0c2-4df4-9e69-fee01f00db31" + DisplayName = "Group 01" + } + } + Mock -CommandName Get-AzureADDirectoryRoleTemplate -MockWith { + return @{ + ObjectId = "17315797-102d-40b4-93e0-432062caca18" + DisplayName = "Compliance Administrator" + } + } + Mock -CommandName Get-AzureADMSNamedLocationPolicy -MockWith { + return @{ + Id = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + DisplayName = "Contoso LAN" + } + } + + } + + It "Should return Present from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It "Should return true from the Test method" { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "Policy exists but it should not" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationEnforcedRestrictionsIsEnabled = $True + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + CloudAppSecurityIsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + DisplayName = "Allin" + Ensure = "Absent" + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + ExcludeGroups = @("Group 01") + ExcludeLocations = "Contoso LAN" + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + ExcludeRoles = @("Compliance Administrator") + ExcludeUsers = "alexw@contoso.com" + GlobalAdminAccount = $Credsglobaladmin + GrantControlOperator = "AND" + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + IncludeApplications = @("All") + IncludeDeviceStates = @("All") + IncludeGroups = @("Group 01") + IncludeLocations = "AllTrusted" + IncludePlatforms = @("Android", "IOS") + IncludeRoles = @("Compliance Administrator") + IncludeUserActions = @("urn:user:registersecurityinfo") + IncludeUsers = "All" + PersistentBrowserIsEnabled = $True + PersistentBrowserMode = "Always" + SignInFrequencyIsEnabled = $True + SignInFrequencyType = "Days" + SignInFrequencyValue = 5 + SignInRiskLevels = @("High") + State = "disabled" + UserRiskLevels = @("High") + } + + Mock -CommandName Get-AzureADMSConditionalAccessPolicy -MockWith { + return @{ + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + DisplayName = "Allin" + State = "disabled" + Conditions = @{ + Applications = @{ + IncludeApplications = @("All") + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + IncludeUserActions = @("urn:user:registersecurityinfo") + } + Users = @{ + IncludeUsers = "All" + ExcludeUsers = "76d3c3f6-8269-462b-9385-37435cb33f1e" + IncludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + ExcludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + IncludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + ExcludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + } + Platforms = @{ + IncludePlatforms = @("Android", "IOS") + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + } + Locations = @{ + IncludeLocations = "AllTrusted" + ExcludeLocations = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + } + Devices = @{ + IncludeDeviceStates = @("All") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + } + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + SignInRiskLevels = @("High") + UserRiskLevels = @("High") + } + GrantControls = @{ + _Operator = "AND" + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + } + SessionControls = @{ + ApplicationEnforcedRestrictions = @{ + IsEnabled = $True + } + CloudAppSecurity = @{ + IsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + } + SignInFrequency = @{ + IsEnabled = $True + Type = "Days" + Value = 5 + } + PersistentBrowser = @{ + IsEnabled = $True + Mode = "Always" + } + } + } + } + Mock -CommandName Get-AzureADUser -MockWith { + return @{ + ObjectId = "76d3c3f6-8269-462b-9385-37435cb33f1e" + UserPrincipalName = "alexw@contoso.com" + } + } + Mock -CommandName Get-AzureADGroup -MockWith { + return @{ + ObjectId = "f1eb1a09-c0c2-4df4-9e69-fee01f00db31" + DisplayName = "Group 01" + } + } + Mock -CommandName Get-AzureADDirectoryRoleTemplate -MockWith { + return @{ + ObjectId = "17315797-102d-40b4-93e0-432062caca18" + DisplayName = "Compliance Administrator" + } + } + Mock -CommandName Get-AzureADMSNamedLocationPolicy -MockWith { + return @{ + Id = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + DisplayName = "Contoso LAN" + } + } + } + + It "Should return Present from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It "Should return false from the Test method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should remove the policy from the Set method" { + Set-TargetResource @testParams + Should -Invoke -CommandName Remove-AzureADMSConditionalAccessPolicy -Exactly 1 + } + } + + Context -Name "ReverseDSC Tests" -Fixture { + BeforeAll { + $testParams = @{ + GlobalAdminAccount = $GlobalAdminAccount + } + + Mock -CommandName Get-AzureADMSConditionalAccessPolicy -MockWith { + return @{ + Id = "bcc0cf19-ee89-46f0-8e12-4b89123ee6f9" + DisplayName = "Allin" + State = "disabled" + Conditions = @{ + Applications = @{ + IncludeApplications = @("All") + ExcludeApplications = @("00000012-0000-0000-c000-000000000000", "Office365") + IncludeUserActions = @("urn:user:registersecurityinfo") + } + Users = @{ + IncludeUsers = "All" + ExcludeUsers = "76d3c3f6-8269-462b-9385-37435cb33f1e" + IncludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + ExcludeGroups = @("f1eb1a09-c0c2-4df4-9e69-fee01f00db31") + IncludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + ExcludeRoles = @("17315797-102d-40b4-93e0-432062caca18") + } + Platforms = @{ + IncludePlatforms = @("Android", "IOS") + ExcludePlatforms = @("Windows", "WindowsPhone", "MacOS") + } + Locations = @{ + IncludeLocations = "AllTrusted" + ExcludeLocations = "9e4ca5f3-0ba9-4257-b906-74d3038ac970" + } + Devices = @{ + IncludeDeviceStates = @("All") + ExcludeDeviceStates = @("Compliant", "DomainJoined") + } + ClientAppTypes = @("Browser", "MobileAppsAndDesktopClients") + SignInRiskLevels = @("High") + UserRiskLevels = @("High") + } + GrantControls = @{ + _Operator = "AND" + BuiltInControls = @("Mfa", "CompliantDevice", "DomainJoinedDevice", "ApprovedApplication", "CompliantApplication") + } + SessionControls = @{ + ApplicationEnforcedRestrictions = @{ + IsEnabled = $True + } + CloudAppSecurity = @{ + IsEnabled = $True + CloudAppSecurityType = "MonitorOnly" + } + SignInFrequency = @{ + IsEnabled = $True + Type = "Days" + Value = 5 + } + PersistentBrowser = @{ + IsEnabled = $True + Mode = "Always" + } + } + } + } + } + + It "Should Reverse Engineer resource from the Export method" { + Export-TargetResource @testParams + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsChannelTab.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsChannelTab.Tests.ps1 new file mode 100644 index 0000000000..dac256e9ff --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsChannelTab.Tests.ps1 @@ -0,0 +1,336 @@ +[CmdletBinding()] +param( +) +$M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` + -ChildPath "..\..\Unit" ` + -Resolve +$CmdletModule = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath "\Stubs\Microsoft365.psm1" ` + -Resolve) +$GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath "\Stubs\Generic.psm1" ` + -Resolve) +Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` + -ChildPath "\UnitTestHelper.psm1" ` + -Resolve) + +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource "TeamsChannelTab" -GenericStubModule $GenericStubPath + +Describe -Name $Global:DscHelper.DescribeHeader -Fixture { + InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { + Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope + + BeforeAll { + $secpasswd = ConvertTo-SecureString "Pass@word1" -AsPlainText -Force + $GlobalAdminAccount = New-Object System.Management.Automation.PSCredential ("tenantadmin", $secpasswd) + + Mock -CommandName Update-M365DSCExportAuthenticationResults -MockWith { + return @{} + } + + Mock -CommandName Get-M365DSCExportContentForResource -MockWith { + + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + + Mock -CommandName New-M365DSCTeamsChannelTab -MockWith { + } + + Mock -CommandName Set-M365DSCTeamsChannelTab -MockWith { + } + + Mock -CommandName Remove-MgTeamChannelTab -MockWith { + } + } + + # Test contexts + Context -Name "When the Tab doesn't exist but should" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationId = "12345"; + CertificateThumbprint = "ABCDEF1234567890"; + ChannelName = "General"; + ContentUrl = "https://contoso.com"; + DisplayName = "TestTab" + Ensure = "Present" + SortOrderIndex = "10100"; + TeamName = "Contoso Team"; + TeamsApp = "com.microsoft.teamspace.tab.web"; + TenantId = 'contoso.onmicrosoft.com'; + WebSiteUrl = "https://contoso.com"; + } + + Mock -CommandName Get-Team -MockWith { + return @{ + GroupId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Team" + } + } + + Mock -CommandName Get-MgTeamChannel -MockWith { + return @{ + Id = "67890-67890-67890-67890-67890" + DisplayName = "General" + } + } + + Mock -CommandName Get-M365DSCTeamChannelTab -MockWith { + return $null + } + } + + It "Should return absent from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Absent' + } + + It "Should return false from the Test method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should create the tab in the Set method" { + Set-TargetResource @testParams + Should -Invoke -CommandName New-M365DSCTeamsChannelTab -Exactly 1 + } + } + + Context -Name "The tab exists but is not in the Desired State" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationId = "12345"; + CertificateThumbprint = "ABCDEF1234567890"; + ChannelName = "General"; + ContentUrl = "https://contoso.com"; + DisplayName = "TestTab" + Ensure = "Present" + SortOrderIndex = "10100"; + TeamName = "Contoso Team"; + TeamsApp = "com.microsoft.teamspace.tab.web"; + TenantId = 'contoso.onmicrosoft.com'; + WebSiteUrl = "https://contoso.com"; + } + + Mock -CommandName Get-Team -MockWith { + return @{ + GroupId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Team" + } + } + + Mock -CommandName Get-MgTeamChannel -MockWith { + return @{ + Id = "67890-67890-67890-67890-67890" + DisplayName = "General" + } + } + + Mock -CommandName Get-MgTeamChannelTab -MockWith { + return @{ + + } + } + + Mock -CommandName Get-M365DSCTeamChannelTab -MockWith { + return @{ + id = "12345-12345-12345-12345-12345" + displayName = "TestTab" + sortOrderIndex = "11100" #Drift + webUrl = "https://contoso.com" + configuration = @{ + entityId = $null + contentUrl = "https://contoso.com" + websiteUrl = "https://contoso.com" + removeUrl = $null + } + teamsApp = @{ + id = "com.microsoft.teamspace.tab.web" + } + } + } + } + + It "Should return Present from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It "Should return false from the Test method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should update the settings from the Set method" { + Set-TargetResource @testParams + Should -Invoke -CommandName Set-M365DSCTeamsChannelTab -Exactly 1 + } + } + + Context -Name "The tab exists and is already in the Desired State" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationId = "12345"; + CertificateThumbprint = "ABCDEF1234567890"; + ChannelName = "General"; + ContentUrl = "https://contoso.com"; + DisplayName = "TestTab" + Ensure = "Present" + SortOrderIndex = "10100"; + TeamName = "Contoso Team"; + TeamsApp = "com.microsoft.teamspace.tab.web"; + TenantId = 'contoso.onmicrosoft.com'; + WebSiteUrl = "https://contoso.com"; + } + + Mock -CommandName Get-Team -MockWith { + return @{ + GroupId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Team" + } + } + + Mock -CommandName Get-MgTeamChannel -MockWith { + return @{ + Id = "67890-67890-67890-67890-67890" + DisplayName = "General" + } + } + + Mock -CommandName Get-M365DSCTeamChannelTab -MockWith { + return @{ + id = "12345-12345-12345-12345-12345" + displayName = "TestTab" + sortOrderIndex = "10100" + webUrl = "https://contoso.com" + configuration = @{ + entityId = $null + contentUrl = "https://contoso.com" + websiteUrl = "https://contoso.com" + removeUrl = $null + } + teamsApp = @{ + id = "com.microsoft.teamspace.tab.web" + } + } + } + } + + It "Should return Present from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It "Should return true from the Test method" { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "Policy exists but it should not" -Fixture { + BeforeAll { + $testParams = @{ + ApplicationId = "12345"; + CertificateThumbprint = "ABCDEF1234567890"; + ChannelName = "General"; + ContentUrl = "https://contoso.com"; + DisplayName = "TestTab" + Ensure = "Absent" + SortOrderIndex = "10100"; + TeamName = "Contoso Team"; + TeamsApp = "com.microsoft.teamspace.tab.web"; + TenantId = 'contoso.onmicrosoft.com'; + WebSiteUrl = "https://contoso.com"; + } + + Mock -CommandName Get-Team -MockWith { + return @{ + GroupId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Team" + } + } + + Mock -CommandName Get-MgTeamChannel -MockWith { + return @{ + Id = "67890-67890-67890-67890-67890" + DisplayName = "General" + } + } + + Mock -CommandName Get-M365DSCTeamChannelTab -MockWith { + return @{ + id = "12345-12345-12345-12345-12345" + displayName = "TestTab" + sortOrderIndex = "11100" #Drift + webUrl = "https://contoso.com" + configuration = @{ + entityId = $null + contentUrl = "https://contoso.com" + websiteUrl = "https://contoso.com" + removeUrl = $null + } + teamsApp = @{ + id = "com.microsoft.teamspace.tab.web" + } + } + } + } + + It "Should return Present from the Get method" { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It "Should return false from the Test method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should remove the policy from the Set method" { + Set-TargetResource @testParams + Should -Invoke -CommandName Remove-MgTeamChannelTab -Exactly 1 + } + } + + Context -Name "ReverseDSC Tests" -Fixture { + BeforeAll { + $testParams = @{ + GlobalAdminAccount = $GlobalAdminAccount + } + + Mock -CommandName Get-Team -MockWith { + return @{ + GroupId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Team" + } + } + + Mock -CommandName Get-MgTeamChannel -MockWith { + return @{ + Id = "67890-67890-67890-67890-67890" + DisplayName = "General" + } + } + + Mock -CommandName Get-M365DSCTeamChannelTab -MockWith { + return @{ + id = "12345-12345-12345-12345-12345" + displayName = "TestTab" + sortOrderIndex = "11100" #Drift + webUrl = "https://contoso.com" + configuration = @{ + entityId = $null + contentUrl = "https://contoso.com" + websiteUrl = "https://contoso.com" + removeUrl = $null + } + teamsApp = @{ + id = "com.microsoft.teamspace.tab.web" + } + } + } + } + + It "Should Reverse Engineer resource from the Export method" { + Export-TargetResource @testParams + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope diff --git a/Tests/Unit/Stubs/Generic.psm1 b/Tests/Unit/Stubs/Generic.psm1 index 706c0a2364..1ef3f7ef52 100644 --- a/Tests/Unit/Stubs/Generic.psm1 +++ b/Tests/Unit/Stubs/Generic.psm1 @@ -196,7 +196,7 @@ function New-M365DSCConnection [Parameter(Mandatory = $true)] [ValidateSet("Azure", "AzureAD", "SharePointOnline", "ExchangeOnline", 'Intune', ` "SecurityComplianceCenter", "MSOnline", "PnP", "PowerPlatforms", ` - "MicrosoftTeams", "SkypeForBusiness")] + "MicrosoftTeams", "SkypeForBusiness", "MicrosoftGraph")] [System.String] $Platform,