DSCResources/MSFT_PPTenantIsolationSettings/MSFT_PPTenantIsolationSettings.psm1

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Yes')]
        [System.String]
        $IsSingleInstance,

        [Parameter()]
        [System.Boolean]
        $Enabled = $true,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Rules,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $RulesToInclude,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $RulesToExclude,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    Write-Verbose -Message 'Getting the Power Platform Tenant Isolation Settings Configuration'

    if ($PSBoundParameters.ContainsKey("Rules") -and `
        ($PSBoundParameters.ContainsKey("RulesToInclude") -or `
                $PSBoundParameters.ContainsKey("RulesToExclude")))
    {
        $message = 'You cannot specify Rules and RulesToInclude/RulesToExclude.'
        Add-M365DSCEvent -Message $message -EntryType 'Error' `
            -EventID 1 -Source $($MyInvocation.MyCommand.Source)
        throw $message
    }

    $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters

    $tenantid = (Get-MgContext).TenantId

    $ConnectionMode = New-M365DSCConnection -Workload 'PowerPlatforms' `
    -InboundParameters $PSBoundParameters

    #Ensure the proper dependencies are installed in the current environment.
    Confirm-M365DSCDependencies

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", ""
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion

    try
    {
        $tenantIsolationPolicy = Get-PowerAppTenantIsolationPolicy -TenantId $tenantid

        [Array]$allowedTenants = $tenantIsolationPolicy.properties.allowedTenants | ForEach-Object {
            $directions = $_.direction
            if ($directions.inbound -eq $true)
            {
                if ($directions.outbound -eq $true)
                {
                    $direction = 'Both'
                }
                else
                {
                    $direction = 'Inbound'
                }
            }
            elseif ($directions.outbound -eq $true)
            {
                $direction = 'Outbound'
            }
            else
            {
                $direction = 'unknown'
            }

            return @{
                TenantName = $_.tenantId
                Direction  = $direction
            }
        }

        return @{
            IsSingleInstance      = 'Yes'
            Enabled               = ($tenantIsolationPolicy.properties.isDisabled -eq $false)
            Rules                 = $allowedTenants
            Credential            = $Credential
        }
    }
    catch
    {
        New-M365DSCLogEntry -Message 'Error retrieving data:' `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return @{}
    }
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Yes')]
        [System.String]
        $IsSingleInstance,

        [Parameter()]
        [System.Boolean]
        $Enabled = $true,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Rules,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $RulesToInclude,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $RulesToExclude,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    Write-Verbose -Message 'Setting Power Platform Tenant Isolation Settings configuration'

    if ($PSBoundParameters.ContainsKey("Rules") -and `
        ($PSBoundParameters.ContainsKey("RulesToInclude") -or `
                $PSBoundParameters.ContainsKey("RulesToExclude")))
    {
        $message = 'You cannot specify Rules and RulesToInclude/RulesToExclude.'
        Add-M365DSCEvent -Message $message -EntryType 'Error' `
            -EventID 1 -Source $($MyInvocation.MyCommand.Source)
        throw $message
    }

    #Ensure the proper dependencies are installed in the current environment.
    Confirm-M365DSCDependencies

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", ""
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion

    $ConnectionMode = New-M365DSCConnection -Workload 'PowerPlatforms' `
        -InboundParameters $PSBoundParameters

    $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters

    $tenantid = (Get-MgContext).TenantId

    $tenantIsolationPolicy = Get-PowerAppTenantIsolationPolicy -TenantId $tenantid

    if ($tenantIsolationPolicy.Properties.isDisabled -ne -not $Enabled)
    {
        $tenantIsolationPolicy.Properties.isDisabled = -not $Enabled
    }

    [Array]$existingAllowedRules = $tenantIsolationPolicy.Properties.allowedTenants

    if ($PSBoundParameters.ContainsKey("Rules"))
    {
        Write-Verbose "Processing parameter Rules"
        foreach ($rule in $Rules)
        {
            # Check if Rules exist
            $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName

            $direction = [PSCustomObject]@{
                inbound  = $false
                outbound = $false
            }

            $existingRule = $existingAllowedRules | Where-Object -FilterScript { $_.tenantId -eq $ruleTenantId }
            if ($null -eq $existingRule)
            {
                Write-Verbose "Rule for $($rule.TenantName) does not exist. Adding with direction $($rule.Direction)"
                switch ($rule.Direction)
                {
                    'Inbound'
                    {
                        $direction.inbound = $true
                    }
                    'Outbound'
                    {
                        $direction.outbound = $true
                    }
                    'Both'
                    {
                        $direction.inbound = $true
                        $direction.outbound = $true
                    }
                }

                $newRule = [PSCustomObject]@{
                    tenantId          = $ruleTenantId
                    tenantDisplayName = ''
                    direction         = $direction
                }

                $existingAllowedRules += $newRule
            }
            else
            {
                Write-Verbose "Rule for $($rule.TenantName) exists. Setting specified direction."
                switch ($rule.Direction)
                {
                    'Inbound'
                    {
                        $existingRule.Direction.inbound = $true
                        $existingRule.Direction.outbound = $false
                    }
                    'Outbound'
                    {
                        $existingRule.Direction.inbound = $false
                        $existingRule.Direction.outbound = $true
                    }
                    'Both'
                    {
                        $existingRule.Direction.inbound = $true
                        $existingRule.Direction.outbound = $true
                    }
                }
            }
        }

        $removeRules = @()
        foreach ($existingRule in $existingAllowedRules)
        {
            # Check if rules are not in the specified list
            if ($null -eq ($Rules | Where-Object -FilterScript { (Get-M365TenantId -TenantName $_.TenantName) -eq $existingRule.tenantId }))
            {
                Write-Verbose "Rule for tenant id $($existingRule.tenantId) does not exist in the Rules parameter. Deleting rule."
                $removeRules += $existingRule.tenantId
            }
        }
        [Array]$newRules = $existingAllowedRules | Where-Object -FilterScript { $_.tenantId -notin $removeRules }
        $tenantIsolationPolicy.Properties.allowedTenants = $newRules
    }

    if ($PSBoundParameters.ContainsKey("RulesToInclude"))
    {
        Write-Verbose "Processing parameter RulesToInclude"
        foreach ($rule in $RulesToInclude)
        {
            Write-Verbose "Checking rule for TenantName $($rule.TenantName) with direction $($rule.Direction)"
            $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName

            $direction = [PSCustomObject]@{
                inbound  = $false
                outbound = $false
            }

            $existingRule = $existingAllowedRules | Where-Object -FilterScript { $_.tenantId -eq $ruleTenantId }
            if ($null -eq $existingRule)
            {
                Write-Verbose "Rule does not exist. Adding with direction $($rule.Direction)"
                # Rule does not exist, add rule
                switch ($rule.Direction)
                {
                    'Inbound'
                    {
                        $direction.inbound = $true
                    }
                    'Outbound'
                    {
                        $direction.outbound = $true
                    }
                    'Both'
                    {
                        $direction.inbound = $true
                        $direction.outbound = $true
                    }
                }

                $newRule = [PSCustomObject]@{
                    tenantId          = $ruleTenantId
                    tenantDisplayName = ''
                    direction         = $direction
                }

                $existingAllowedRules += $newRule
            }
            else
            {
                Write-Verbose "Rule exists. Setting specified direction."
                switch ($rule.Direction)
                {
                    'Inbound'
                    {
                        $existingRule.Direction.inbound = $true
                        $existingRule.Direction.outbound = $false
                    }
                    'Outbound'
                    {
                        $existingRule.Direction.inbound = $false
                        $existingRule.Direction.outbound = $true
                    }
                    'Both'
                    {
                        $existingRule.Direction.inbound = $true
                        $existingRule.Direction.outbound = $true
                    }
                }
            }
        }
        $tenantIsolationPolicy.Properties.allowedTenants = $existingAllowedRules
    }

    if ($PSBoundParameters.ContainsKey("RulesToExclude"))
    {
        Write-Verbose "Processing parameter RulesToExclude"
        foreach ($rule in $RulesToExclude)
        {
            Write-Verbose "Checking rule for TenantName $($rule.TenantName)"
            $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName

            $removeRules = @()
            if ($null -ne ($existingAllowedRules | Where-Object -FilterScript { $_.tenantId -eq $ruleTenantId }))
            {
                Write-Verbose "Rule for $($rule.TenantName) is currently configured in the rules. Deleting rule."
                $removeRules += $ruleTenantId
            }
        }

        [Array]$newRules = $existingAllowedRules | Where-Object -FilterScript { $_.tenantId -notin $removeRules }
        $tenantIsolationPolicy.Properties.allowedTenants = $newRules
    }

    Write-Verbose "Saving changes to the tenant"
    $null = Set-PowerAppTenantIsolationPolicy -TenantIsolationPolicy $tenantIsolationPolicy -TenantId $tenantId
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Yes')]
        [System.String]
        $IsSingleInstance,

        [Parameter()]
        [System.Boolean]
        $Enabled = $true,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Rules,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $RulesToInclude,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $RulesToExclude,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    Write-Verbose -Message 'Testing Power Platform Tenant Isolation Settings configuration'

    #Ensure the proper dependencies are installed in the current environment.
    Confirm-M365DSCDependencies

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", ""
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion

    $CurrentValues = Get-TargetResource @PSBoundParameters

    Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)"
    Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)"

    $result = $true
    $driftedRules = @{}
    if ($PSBoundParameters.ContainsKey("Rules"))
    {
        Write-Verbose "Processing parameter Rules"
        foreach ($rule in $Rules)
        {
            Write-Verbose "Checking Rule for TenantName $($rule.TenantName)."
            $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName

            $existingRule = $CurrentValues.Rules | Where-Object -FilterScript { $_.TenantName -eq $ruleTenantId }
            if ($null -eq $existingRule)
            {
                Write-Verbose "Rule for $($rule.TenantName) does not exist."
                $driftedRules.($rule.TenantName) = @{
                    CurrentValue = "Rule does not exist"
                    DesiredValue = "Direction: $($rule.Direction)"
                }
                $result = $false
            }
            else
            {
                Write-Verbose "Rule for $($rule.TenantName) exists. Checking specified direction."
                if ($rule.Direction -ne $existingRule.Direction)
                {
                    Write-Verbose "Direction for rule incorrect: Current = $($existingRule.Direction) / Desired = $($rule.Direction)"
                    $driftedRules.($rule.TenantName) = @{
                        CurrentValue = "Direction: $($existingRule.Direction)"
                        DesiredValue = "Direction: $($rule.Direction)"
                    }
                    $result = $false
                }
            }
        }

        foreach ($existingRule in $CurrentValues.Rules)
        {
            # Check if rules are not in the specified list
            if ($null -eq ($Rules | Where-Object -FilterScript { (Get-M365TenantId -TenantName $_.TenantName) -eq $existingRule.TenantName }))
            {
                Write-Verbose "Rule for tenant id $($existingRule.TenantName) does not exist in the Desired State."

                $driftedRules.($existingRule.TenantName) = @{
                    CurrentValue = "Direction: $($existingRule.Direction)"
                    DesiredValue = "Should not exist"
                }
                $result = $false
            }
        }
    }

    if ($PSBoundParameters.ContainsKey("RulesToInclude"))
    {
        Write-Verbose "Processing parameter RulesToInclude"
        $driftedRules = @{}
        foreach ($rule in $RulesToInclude)
        {
            Write-Verbose "Checking Rule for TenantName $($rule.TenantName)."
            $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName

            $existingRule = $CurrentValues.Rules | Where-Object -FilterScript { $_.TenantName -eq $ruleTenantId }
            if ($null -eq $existingRule)
            {
                Write-Verbose "Rule for $($rule.TenantName) does not exist."
                $driftedRules.($rule.TenantName) = @{
                    CurrentValue = "Rule does not exist"
                    DesiredValue = "Direction: $($rule.Direction)"
                }
                $result = $false
            }
            else
            {
                Write-Verbose "Rule for $($rule.TenantName) exists. Checking specified direction."
                if ($rule.Direction -ne $existingRule.Direction)
                {
                    Write-Verbose "Direction for rule incorrect: Current = $($existingRule.Direction) / Desired = $($rule.Direction)"
                    $driftedRules.($rule.TenantName) = @{
                        CurrentValue = "Direction: $($existingRule.Direction)"
                        DesiredValue = "Direction: $($rule.Direction)"
                    }
                    $result = $false
                }
            }
        }
    }

    if ($PSBoundParameters.ContainsKey("RulesToExclude"))
    {
        Write-Verbose "Processing parameter RulesToExclude"
        $driftedRules = @{}
        foreach ($rule in $RulesToExclude)
        {
            Write-Verbose "Checking Rule for TenantName $($rule.TenantName)."
            $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName

            $existingRule = $CurrentValues.Rules | Where-Object -FilterScript { $_.TenantName -eq $ruleTenantId }
            if ($null -ne $existingRule)
            {
                Write-Verbose "Rule for $($rule.TenantName) exists."
                $driftedRules.($rule.TenantName) = @{
                    CurrentValue = "Direction: $($existingRule.Direction)"
                    DesiredValue = "Should not exist"
                }
                $result = $false
            }
        }
    }

    if ($result -eq $false)
    {
        $message = "Tenant Isolation Rules not in the Desired State:`n"
        $message += "<Rules>`n"
        foreach ($driftedRule in $driftedRules.GetEnumerator())
        {
            $message += " <Rule>`n"
            $message += " <TenantName>$($driftedRule.Name)</TenantName>`n"
            $message += " <CurrentValue>$($driftedRule.Value.CurrentValue)</CurrentValue>`n"
            $message += " <DesiredValue>$($driftedRule.Value.DesiredValue)</DesiredValue>`n"
            $message += " </Rule>`n"
        }
        $message += "</Rules>"
        Add-M365DSCEvent -Message $message -EntryType 'Error' `
            -EventID 1 -Source $($MyInvocation.MyCommand.Source)
        Write-Verbose -Message "Test-TargetResource returned False"
        return $false
    }

    $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues `
        -Source $($MyInvocation.MyCommand.Source) `
        -DesiredValues $PSBoundParameters `
        -ValuesToCheck @("Enabled")

    Write-Verbose -Message "Test-TargetResource returned $TestResult"

    return $TestResult
}

function Export-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    $ConnectionMode = New-M365DSCConnection -Workload 'PowerPlatforms' `
        -InboundParameters $PSBoundParameters

    $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters

    #Ensure the proper dependencies are installed in the current environment.
    Confirm-M365DSCDependencies

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", ""
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion

    try
    {
        $dscContent = ''

        $Params = @{
            IsSingleInstance      = 'Yes'
            Credential            = $Credential
        }

        $Results = Get-TargetResource @Params
        if ($Results.Rules.Count -gt 0)
        {
            $Results.Rules = Get-M365DSCTenantIsolationRule $Results.Rules
        }
        $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode `
            -Results $Results
        $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName `
            -ConnectionMode $ConnectionMode `
            -ModulePath $PSScriptRoot `
            -Results $Results `
            -Credential $Credential
        $dscContent += $currentDSCBlock

        Save-M365DSCPartialExport -Content $currentDSCBlock `
            -FileName $Global:PartialExportFileName

        Write-Host $Global:M365DSCEmojiGreenCheckMark

        return $dscContent
    }
    catch
    {
        Write-Host $Global:M365DSCEmojiRedX

        New-M365DSCLogEntry -Message "Error during Export:" `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return ''
    }
}

function Get-M365TenantId
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $TenantName
    )

    if ($TenantName -notmatch ".onmicrosoft.com$")
    {
        $TenantName += '.onmicrosoft.com'
    }

    $result = Invoke-WebRequest "https://login.windows.net/$TenantName/.well-known/openid-configuration" -UseBasicParsing -Verbose:$false
    $jsonResult = $result | ConvertFrom-Json
    return $jsonResult.token_endpoint.Split('/')[3]
}

function Get-M365DSCTenantIsolationRule
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Mandatory = $true)]
        [System.Collections.ArrayList]
        $Rules
    )

    $StringContent = "@("
    foreach ($rule in $Rules)
    {
        $StringContent += "ule {r`n"
        $StringContent += " TenantName = '" + $rule.TenantName + "'`r`n"
        $StringContent += " Direction = '" + $rule.Direction + "'`r`n"
        $StringContent += " }`r`n"
    }
    $StringContent += " )"
    return $StringContent
}

Export-ModuleMember -Function *-TargetResource