Modules/M365DSCIntuneUtil.psm1

<#
.SYNOPSIS
    Converts Microsoft Graph Intune policy assignments into DSC-friendly hashtables.
 
.DESCRIPTION
    Transforms assignment objects returned by Intune policy endpoints into hashtables used by Microsoft365DSC.
    It resolves group display names and, when enabled, enriches assignments with device filter metadata.
 
.PARAMETER Assignments
    Specifies the Intune assignment objects returned by Microsoft Graph.
 
.PARAMETER IncludeDeviceFilter
    Indicates whether assignment filter type, identifier, and display name should be included.
 
.OUTPUTS
    System.Collections.Hashtable[]
#>

function ConvertFrom-IntunePolicyAssignment
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable[]])]
    param (
        [Parameter(Mandatory = $true)]
        [Array]
        $Assignments,

        [Parameter()]
        [System.Boolean]
        $IncludeDeviceFilter = $true
    )

    if ($null -eq $Script:IntuneAssignmentFilters)
    {
        $Script:IntuneAssignmentFilters = Get-MgBetaDeviceManagementAssignmentFilter -All -ErrorAction SilentlyContinue | ForEach-Object {
            @{
                FilterId    = $_.Id
                DisplayName = $_.DisplayName
            }
        }
    }

    $assignmentResult = @()
    foreach ($assignment in $Assignments)
    {
        $hashAssignment = [ordered]@{}
        if ($null -ne $assignment.Target.'@odata.type')
        {
            $dataType = $assignment.Target.'@odata.type'
        }
        else
        {
            $dataType = $assignment.Target.'@odata.type'
        }

        if ($null -ne $assignment.Target.groupId)
        {
            $groupId = $assignment.Target.groupId
        }
        else
        {
            $groupId = $assignment.Target.groupId
        }

        if ($null -ne $assignment.Target.collectionId)
        {
            $collectionId = $assignment.Target.collectionId
        }
        else
        {
            $collectionId = $assignment.Target.collectionId
        }

        $hashAssignment.Add('dataType', $dataType)
        if (-not [string]::IsNullOrEmpty($groupId))
        {
            $hashAssignment.Add('groupId', $groupId)

            $group = Get-MgGroup -GroupId ($groupId) -ErrorAction SilentlyContinue
            if ($null -ne $group)
            {
                $groupDisplayName = $group.DisplayName
            }
        }
        if ($dataType -eq '#microsoft.graph.allLicensedUsersAssignmentTarget')
        {
            $groupDisplayName = 'All users'
        }
        if ($dataType -eq '#microsoft.graph.allDevicesAssignmentTarget')
        {
            $groupDisplayName = 'All devices'
        }
        if ($null -ne $groupDisplayName)
        {
            $hashAssignment.Add('groupDisplayName', $groupDisplayName)
        }
        if (-not [string]::IsNullOrEmpty($collectionId))
        {
            $hashAssignment.Add('collectionId', $collectionId)
        }
        if ($IncludeDeviceFilter)
        {
            if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterType)
            {
                $hashAssignment.Add('deviceAndAppManagementAssignmentFilterType', $assignment.Target.DeviceAndAppManagementAssignmentFilterType.ToString())
            }
            if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterId)
            {
                $filterId = $assignment.Target.DeviceAndAppManagementAssignmentFilterId
                $hashAssignment.Add('deviceAndAppManagementAssignmentFilterId', $filterId)
                $hashAssignment.Add('deviceAndAppManagementAssignmentFilterDisplayName', (($Script:IntuneAssignmentFilters | Where-Object -Property FilterId -EQ $filterId).DisplayName))
            }
        }

        $assignmentResult += $hashAssignment
    }

    return ,$assignmentResult
}

<#
.SYNOPSIS
    Converts DSC assignment hashtables into Intune policy assignment payloads.
 
.DESCRIPTION
    Builds Microsoft Graph assignment payloads from DSC-style hashtables for policy assignment operations.
    It resolves groups by id or display name and optionally adds assignment filter values.
 
.PARAMETER Assignments
    Specifies the DSC assignment hashtables to convert.
 
.PARAMETER IncludeDeviceFilter
    Indicates whether assignment filter information should be added to generated targets.
 
.OUTPUTS
    Hashtable[]
#>

function ConvertTo-IntunePolicyAssignment
{
    [CmdletBinding()]
    [OutputType([Hashtable[]])]
    param (
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        $Assignments,

        [Parameter()]
        [System.Boolean]
        $IncludeDeviceFilter = $true
    )

    if ($null -eq $Script:IntuneAssignmentFilters)
    {
        $Script:IntuneAssignmentFilters = Get-MgBetaDeviceManagementAssignmentFilter -All -ErrorAction SilentlyContinue | ForEach-Object {
            @{
                FilterId    = $_.Id
                DisplayName = $_.DisplayName
            }
        }
    }

    if ($null -eq $Assignments)
    {
        return ,@()
    }

    $assignmentResult = @()
    foreach ($assignment in $Assignments)
    {
        $target = @{
            '@odata.type' = $assignment.dataType
        }
        if ($IncludeDeviceFilter)
        {
            if ($null -ne $assignment.DeviceAndAppManagementAssignmentFilterType -and $assignment.DeviceAndAppManagementAssignmentFilterType -ne 'none')
            {
                $filter = $Script:IntuneAssignmentFilters | Where-Object -Property FilterId -EQ $assignment.DeviceAndAppManagementAssignmentFilterId
                if ($null -eq $filter)
                {
                    $filter = $Script:IntuneAssignmentFilters | Where-Object -Property DisplayName -EQ $assignment.DeviceAndAppManagementAssignmentFilterDisplayName
                    if ($null -eq $filter)
                    {
                        Write-Warning -Message "Assignment filter with DisplayName {$($assignment.DeviceAndAppManagementAssignmentFilterDisplayName)} not found in the directory. Please update your DSC resource extract with the correct filterId or filterDisplayName."
                    }
                }

                if ($null -ne $filter)
                {
                    $target.Add('deviceAndAppManagementAssignmentFilterType', $assignment.DeviceAndAppManagementAssignmentFilterType)
                    $target.Add('deviceAndAppManagementAssignmentFilterId', $filter.FilterId)
                }
            }
        }
        if ($assignment.dataType -like '*CollectionAssignmentTarget')
        {
            $target.Add('collectionId', $assignment.collectionId)
        }
        elseif ($assignment.dataType -like '*GroupAssignmentTarget')
        {
            $group = $null
            if (-not [System.String]::IsNullOrEmpty($assignment.groupId))
            {
                $group = Get-MgGroup -GroupId ($assignment.groupId) -ErrorAction SilentlyContinue
            }
            if ($null -eq $group -and -not [System.String]::IsNullOrEmpty($assignment.groupDisplayName))
            {
                $escapedName = $assignment.groupDisplayName -replace "'", "''"
                [array]$group = Get-MgGroup -Filter "DisplayName eq '$escapedName'" -All -ErrorAction SilentlyContinue
                if ($null -eq $group -or $group.Count -eq 0)
                {
                    Write-Warning "Skipping assignment: groupDisplayName '{$($assignment.groupDisplayName)}' not found."
                    $target = $null
                    $group = $null
                }
                elseif ($group.Count -gt 1)
                {
                    Write-Warning "Skipping assignment: groupDisplayName '{$($assignment.groupDisplayName)}' is not unique."
                    $target = $null
                    $group = $null
                }
            }
            # If group found, add its ID
            if ($null -ne $group)
            {
                $target.Add('groupId', $group.Id)
            }
            elseif ($null -eq $group -and [System.String]::IsNullOrEmpty($assignment.groupDisplayName))
            {
                Write-Warning 'Skipping assignment: missing both groupId and groupDisplayName.'
                $target = $null
            }
        }

        if ($null -ne $target)
        {
            $assignmentResult += @{ target = $target }
        }
    }

    return ,$assignmentResult
}

<#
.SYNOPSIS
    Converts mobile app assignment objects into DSC hashtables.
 
.DESCRIPTION
    Transforms Intune mobile app assignments into hashtables used by Microsoft365DSC resources.
    It includes assignment intent, group display names, optional filter details, and assignment settings when present.
 
.PARAMETER Assignments
    Specifies the mobile app assignment objects returned by Microsoft Graph.
 
.PARAMETER IncludeDeviceFilter
    Indicates whether assignment filter details should be included in the output.
 
.OUTPUTS
    System.Collections.Hashtable[]
#>

function ConvertFrom-IntuneMobileAppAssignment
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable[]])]
    param (
        [Parameter(Mandatory = $true)]
        [Array]
        $Assignments,

        [Parameter()]
        [System.Boolean]
        $IncludeDeviceFilter = $true
    )

    if ($null -eq $Script:IntuneAssignmentFilters)
    {
        $Script:IntuneAssignmentFilters = Get-MgBetaDeviceManagementAssignmentFilter -All -ErrorAction SilentlyContinue | ForEach-Object {
            @{
                FilterId    = $_.Id
                DisplayName = $_.DisplayName
            }
        }
    }

    $assignmentResult = @()
    foreach ($assignment in $Assignments)
    {
        $hashAssignment = @{}
        if ($null -ne $assignment.Target.'@odata.type')
        {
            $dataType = $assignment.Target.'@odata.type'
        }
        else
        {
            $dataType = $assignment.Target.'@odata.type'
        }

        if ($null -ne $assignment.Target.groupId)
        {
            $groupId = $assignment.Target.groupId
        }
        else
        {
            $groupId = $assignment.Target.groupId
        }

        $hashAssignment.Add('dataType', $dataType)
        if (-not [string]::IsNullOrEmpty($groupId))
        {
            $hashAssignment.Add('groupId', $groupId)

            $group = Get-MgGroup -GroupId ($groupId) -ErrorAction SilentlyContinue
            if ($null -ne $group)
            {
                $groupDisplayName = $group.DisplayName
            }
        }

        if ($dataType -eq '#microsoft.graph.allLicensedUsersAssignmentTarget')
        {
            $groupDisplayName = 'All users'
        }
        if ($dataType -eq '#microsoft.graph.allDevicesAssignmentTarget')
        {
            $groupDisplayName = 'All devices'
        }
        if ($null -ne $groupDisplayName)
        {
            $hashAssignment.Add('groupDisplayName', $groupDisplayName)
        }

        $hashAssignment.Add('intent', $assignment.intent.ToString())

        if ($IncludeDeviceFilter)
        {
            if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterType)
            {
                $hashAssignment.Add('deviceAndAppManagementAssignmentFilterType', $assignment.Target.DeviceAndAppManagementAssignmentFilterType.ToString())
            }
            if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterId)
            {
                $filterId = $assignment.Target.DeviceAndAppManagementAssignmentFilterId
                $hashAssignment.Add('deviceAndAppManagementAssignmentFilterId', $filterId)
                $hashAssignment.Add('deviceAndAppManagementAssignmentFilterDisplayName', (($Script:IntuneAssignmentFilters | Where-Object -Property FilterId -EQ $filterId).DisplayName))
            }
        }

        if ($null -ne $assignment.settings -and $assignment.settings.Count -gt 0)
        {
            $settings = (Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $assignment.settings)
            $hashAssignment.Add('assignmentSettings', $settings)
        }

        $assignmentResult += $hashAssignment
    }

    return ,$assignmentResult
}

<#
.SYNOPSIS
    Converts DSC mobile app assignment hashtables into Graph request payloads.
 
.DESCRIPTION
    Creates Intune mobile app assignment payloads from DSC-style hashtables.
    It resolves target groups, applies optional assignment filters, and maps assignment settings into the expected schema.
 
.PARAMETER Assignments
    Specifies the mobile app assignment hashtables to convert.
 
.PARAMETER IncludeDeviceFilter
    Indicates whether assignment filter fields should be emitted in the generated targets.
 
.OUTPUTS
    Hashtable[]
#>

function ConvertTo-IntuneMobileAppAssignment
{
    [CmdletBinding()]
    [OutputType([Hashtable[]])]
    param (
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        $Assignments,

        [Parameter()]
        [System.Boolean]
        $IncludeDeviceFilter = $true
    )

    if ($null -eq $Script:IntuneAssignmentFilters)
    {
        $Script:IntuneAssignmentFilters = Get-MgBetaDeviceManagementAssignmentFilter -All -ErrorAction SilentlyContinue | ForEach-Object {
            @{
                FilterId    = $_.Id
                DisplayName = $_.DisplayName
            }
        }
    }

    if ($null -eq $Assignments)
    {
        return ,@()
    }

    $assignmentResult = @()
    foreach ($assignment in $Assignments)
    {
        $formattedAssignment = @{}
        $target = @{
            '@odata.type' = $assignment.dataType
        }

        # Handle Device Filters
        if ($IncludeDeviceFilter)
        {
            if ($null -ne $assignment.DeviceAndAppManagementAssignmentFilterType -and
                $assignment.DeviceAndAppManagementAssignmentFilterType -ne 'none')
            {
                $filter = $Script:IntuneAssignmentFilters | Where-Object -Property FilterId -EQ $assignment.DeviceAndAppManagementAssignmentFilterId

                if ($null -eq $filter)
                {
                    $filter = $Script:IntuneAssignmentFilters | Where-Object -Property DisplayName -EQ $assignment.DeviceAndAppManagementAssignmentFilterDisplayName
                }

                if ($null -ne $filter)
                {
                    $target.Add('deviceAndAppManagementAssignmentFilterType', $assignment.DeviceAndAppManagementAssignmentFilterType)
                    $target.Add('deviceAndAppManagementAssignmentFilterId', $filter.FilterId)
                }
                else
                {
                    Write-Warning "Assignment filter with DisplayName {$($assignment.DeviceAndAppManagementAssignmentFilterDisplayName)} not found."
                }
            }
        }

        # Add intent (required for app assignments)
        $formattedAssignment.Add('intent', $assignment.intent)

        if ($assignment.dataType -like '*groupAssignmentTarget')
        {
            $group = $null
            if (-not [System.String]::IsNullOrEmpty($assignment.groupId))
            {
                $group = Get-MgGroup -GroupId $assignment.groupId -ErrorAction SilentlyContinue
            }
            # If groupId lookup failed, try by display name
            if ($null -eq $group -and -not [System.String]::IsNullOrEmpty($assignment.groupDisplayName))
            {
                $escapedName = $assignment.groupDisplayName -replace "'", "''"
                [array]$group = Get-MgGroup -Filter "DisplayName eq '$escapedName'" -All -ErrorAction SilentlyContinue
                if ($null -eq $group -or $group.Count -eq 0)
                {
                    Write-Warning "Skipping assignment: groupDisplayName '{$($assignment.groupDisplayName)}' not found."
                    $target = $null
                    $group = $null
                }
                elseif ($group.Count -gt 1)
                {
                    Write-Warning "Skipping assignment: groupDisplayName '{$($assignment.groupDisplayName)}' is not unique."
                    $target = $null
                    $group = $null
                }
            }
            # If group found, add its ID
            if ($null -ne $group)
            {
                $target.Add('groupId', $group.Id)
            }
            elseif ($null -eq $group -and [System.String]::IsNullOrEmpty($assignment.groupDisplayName))
            {
                Write-Warning 'Skipping assignment: missing both groupId and groupDisplayName.'
                $target = $null
            }
        }
        # Add target if valid
        if ($null -ne $target)
        {
            $formattedAssignment.Add('target', $target)

            # Add assignment settings if present
            if ($null -ne $assignment.assignmentSettings)
            {
                $settings = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $assignment.assignmentSettings
                $formattedAssignment.Add('settings', $settings)
                $formattedAssignment.settings.Add('@odata.type', $formattedAssignment.settings.odataType)
                $formattedAssignment.settings.Remove('odataType') | Out-Null
            }
            $assignmentResult += $formattedAssignment
        }
    }
    return ,$assignmentResult
}

<#
.SYNOPSIS
    Retrieves cached Intune configuration policies by templateId or filter.
 
.DESCRIPTION
    Fetches Intune configuration policies from the local cache if available, or queries Microsoft Graph using the provided templateId or filter.
    This function is used to optimize retrieval of configuration policies during DSC operations.
 
.PARAMETER TemplateId
    Specifies the templateId of the configuration policy to retrieve.
 
.PARAMETER Filter
    Specifies an optional OData filter string to apply when querying configuration policies.
 
.OUTPUTS
    List of configuration policy objects from cache or Microsoft Graph.
#>

function Get-M365DSCExportCachedConfigurationPolicies
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [System.String]
        $TemplateId,

        [Parameter(Mandatory = $false)]
        [System.String]
        $Filter
    )

    # Fetch from cache if the filter is empty or matches the templateId filter
    if (-not [System.String]::IsNullOrEmpty($Filter) -and $Filter -like 'templateReference/templateId eq ''*''')
    {
        $cacheValue = [Microsoft365DSC.Intune.ConfigurationPolicyCache]::GetByTemplateId($TemplateId)
        if ($null -ne $cacheValue)
        {
            return $cacheValue
        }
    }

    if (-not [System.String]::IsNullOrEmpty($Filter))
    {
        return Get-MgBetaDeviceManagementConfigurationPolicy -All `
            -Filter $Filter `
            -ErrorAction Stop
    }

    return Get-MgBetaDeviceManagementConfigurationPolicy -All `
        -Filter "templateReference/TemplateId eq '$TemplateId'" `
        -ErrorAction Stop
}

<#
.SYNOPSIS
    Updates assignments on an Intune device configuration policy.
 
.DESCRIPTION
    Builds and submits assignment payloads for a device configuration policy.
    It resolves groups when needed and posts the final assignment set to the selected Graph endpoint.
 
.PARAMETER DeviceConfigurationPolicyId
    Specifies the identifier of the device configuration policy to update.
 
.PARAMETER Targets
    Specifies assignment targets to apply to the policy.
 
.PARAMETER Repository
    Specifies the Graph repository segment used to address the policy resource.
 
.PARAMETER APIVersion
    Specifies the Graph API version to call.
 
.PARAMETER RootIdentifier
    Specifies the root property name used for the assignment payload body.
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Update-DeviceConfigurationPolicyAssignment
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $DeviceConfigurationPolicyId,

        [Parameter()]
        [Array]
        $Targets,

        [Parameter()]
        [System.String]
        $Repository = 'deviceManagement/configurationPolicies',

        [Parameter()]
        [ValidateSet('v1.0', 'beta')]
        [System.String]
        $APIVersion = 'beta',

        [Parameter()]
        [System.String]
        $RootIdentifier = 'assignments'
    )

    try
    {
        $deviceManagementPolicyAssignments = @()
        $Uri = "/$APIVersion/$Repository/$DeviceConfigurationPolicyId/assign"

        foreach ($target in $targets)
        {
            $targetAssignment = @{}
            $formattedTarget = @{'@odata.type' = $target.dataType }
            if ($null -ne $target.runRemediationScript)
            {
                $targetAssignment.Add('runRemediationScript', $target.runRemediationScript)
            }
            if ($null -ne $target.runSchedule)
            {
                $targetAssignment.Add('runSchedule', $target.runSchedule)
            }
            if ($target.target -is [hashtable])
            {
                $target = $target.target
            }
            if (-not $formattedTarget.'@odata.type' -and $target.'@odata.type')
            {
                $formattedTarget.'@odata.type' = $target.'@odata.type'
            }
            if ($target.groupId)
            {
                $group = Get-MgGroup -GroupId ($target.groupId) -ErrorAction SilentlyContinue
                if ($null -eq $group)
                {
                    if ($target.groupDisplayName)
                    {
                        [array]$group = Get-MgGroup -Filter "DisplayName eq '$($target.groupDisplayName -replace "'", "''")'" -All -ErrorAction SilentlyContinue
                        if ($null -eq $group -or $group.Count -eq 0)
                        {
                            $message = "Skipping assignment for the group with DisplayName {$($target.groupDisplayName)} as it could not be found in the directory.`r`n"
                            $message += 'Please update your DSC resource extract with the correct groupId or groupDisplayName.'
                            Write-Warning -Message $message
                            continue
                        }
                        if ($group -and $group.Count -gt 1)
                        {
                            $message = "Skipping assignment for the group with DisplayName {$($target.groupDisplayName)} as it is not unique in the directory.`r`n"
                            $message += 'Please update your DSC resource extract with the correct groupId or a unique group DisplayName.'
                            Write-Warning -Message $message
                            continue
                        }
                    }
                    else
                    {
                        $message = "Skipping assignment for the group with Id {$($target.groupId)} as it could not be found in the directory.`r`n"
                        $message += 'Please update your DSC resource extract with the correct groupId or a unique group DisplayName.'
                        Write-Warning -Message $message
                        continue
                    }
                }
                #Skipping assignment if group not found from either groupId or groupDisplayName
                if ($null -ne $group)
                {
                    $formattedTarget.Add('groupId', $group.Id)
                }
            }
            if ($target.collectionId)
            {
                $formattedTarget.Add('collectionId', $target.collectionId)
            }
            if ($target.deviceAndAppManagementAssignmentFilterType)
            {
                $formattedTarget.Add('deviceAndAppManagementAssignmentFilterType', $target.deviceAndAppManagementAssignmentFilterType)
            }
            if ($target.deviceAndAppManagementAssignmentFilterId)
            {
                $formattedTarget.Add('deviceAndAppManagementAssignmentFilterId', $target.deviceAndAppManagementAssignmentFilterId)
            }
            $targetAssignment.Add('target', $formattedTarget)
            $deviceManagementPolicyAssignments += $targetAssignment
        }

        $body = @{$RootIdentifier = $deviceManagementPolicyAssignments } | ConvertTo-Json -Depth 20
        Write-Verbose -Message $body

        Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $body -ErrorAction Stop
    }
    catch
    {
        New-M365DSCLogEntry -Message 'Error updating data:' `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return $null
    }
}

<#
.SYNOPSIS
    Updates assignments on an Intune app management policy.
 
.DESCRIPTION
    Creates and submits mobile app assignment payloads for the target app policy.
    The function resolves assignment groups and filter settings before posting the payload to Graph.
 
.PARAMETER AppManagementPolicyId
    Specifies the identifier of the app management policy to update.
 
.PARAMETER Assignments
    Specifies the mobile app assignments to apply.
 
.PARAMETER Repository
    Specifies the Graph repository segment used to address the app resource.
 
.PARAMETER APIVersion
    Specifies the Graph API version to call.
 
.PARAMETER RootIdentifier
    Specifies the root property name used for the assignment payload body.
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Update-DeviceAppManagementPolicyAssignment
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $AppManagementPolicyId,

        [Parameter()]
        [Array]
        $Assignments,

        [Parameter()]
        [System.String]
        $Repository = 'deviceAppManagement/mobileApps',

        [Parameter()]
        [ValidateSet('v1.0', 'beta')]
        [System.String]
        $APIVersion = 'beta',

        [Parameter()]
        [System.String]
        $RootIdentifier = 'mobileAppAssignments'
    )

    try
    {
        $appManagementPolicyAssignments = @()
        $Uri = "/$APIVersion/$Repository/$AppManagementPolicyId/assign"

        foreach ($assignment in $Assignments)
        {
            $formattedAssignment = @{
                '@odata.type' = '#microsoft.graph.mobileAppAssignment'
                intent        = $assignment.intent
            }
            if ($assignment.settings)
            {
                $formattedAssignment.Add('settings', $assignment.settings)
            }

            if ($assignment.target -is [hashtable])
            {
                $target = $assignment.target
            }

            $formattedTarget = @{'@odata.type' = $target.dataType }
            if (-not $formattedTarget.'@odata.type' -and $target.'@odata.type')
            {
                $formattedTarget.'@odata.type' = $target.'@odata.type'
            }
            if ($target.groupId)
            {
                $group = Get-MgGroup -GroupId ($target.groupId) -ErrorAction SilentlyContinue
                if ($null -eq $group)
                {
                    if ($target.groupDisplayName)
                    {
                        [array]$group = Get-MgGroup -Filter "DisplayName eq '$($target.groupDisplayName -replace "'", "''")'" -All -ErrorAction SilentlyContinue
                        if ($null -eq $group -or $group.Count -eq 0)
                        {
                            $message = "Skipping assignment for the group with DisplayName {$($target.groupDisplayName)} as it could not be found in the directory.`r`n"
                            $message += 'Please update your DSC resource extract with the correct groupId or groupDisplayName.'
                            Write-Warning -Message $message
                            continue
                        }
                        if ($group -and $group.Count -gt 1)
                        {
                            $message = "Skipping assignment for the group with DisplayName {$($target.groupDisplayName)} as it is not unique in the directory.`r`n"
                            $message += 'Please update your DSC resource extract with the correct groupId or a unique group DisplayName.'
                            Write-Warning -Message $message
                            continue
                        }
                    }
                    else
                    {
                        $message = "Skipping assignment for the group with Id {$($target.groupId)} as it could not be found in the directory.`r`n"
                        $message += 'Please update your DSC resource extract with the correct groupId or a unique group DisplayName.'
                        Write-Warning -Message $message
                        continue
                    }
                }
                #Skipping assignment if group not found from either groupId or groupDisplayName
                if ($null -ne $group)
                {
                    $formattedTarget.Add('groupId', $group.Id)
                }
            }
            if ($target.deviceAndAppManagementAssignmentFilterType)
            {
                $formattedTarget.Add('deviceAndAppManagementAssignmentFilterType', $target.deviceAndAppManagementAssignmentFilterType)
            }
            if ($target.deviceAndAppManagementAssignmentFilterId)
            {
                $formattedTarget.Add('deviceAndAppManagementAssignmentFilterId', $target.deviceAndAppManagementAssignmentFilterId)
            }
            $formattedAssignment.Add('target', $formattedTarget)
            $appManagementPolicyAssignments += $formattedAssignment
        }

        $body = @{ $RootIdentifier = $appManagementPolicyAssignments } | ConvertTo-Json -Depth 20
        Write-Verbose -Message $body

        Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $body -ErrorAction Stop
    }
    catch
    {
        New-M365DSCLogEntry -Message 'Error updating data:' `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return $null
    }
}

<#
.SYNOPSIS
    Synchronizes Intune mobile app category assignments.
 
.DESCRIPTION
    Adds or removes mobile app categories for a target app.
    In compare mode, it computes differences and only applies required adds or removals.
 
.PARAMETER App
    Specifies the mobile app object to update.
 
.PARAMETER Categories
    Specifies the category set to add or compare against.
 
.PARAMETER Compare
    Indicates that categories should be reconciled by difference instead of only added.
#>

function Update-DeviceAppManagementAppCategory
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $App,

        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [Array]
        $Categories,

        [Parameter()]
        [switch]
        $Compare
    )

    if ($Compare)
    {
        [array]$referenceObject = if ($null -ne $App.Categories.DisplayName)
        {
            $App.Categories.DisplayName
        }
        else
        {
            , @()
        }
        [array]$differenceObject = if ($null -ne $Categories.DisplayName)
        {
            $Categories.DisplayName
        }
        else
        {
            , @()
        }
        $delta = Compare-Object -ReferenceObject $referenceObject -DifferenceObject $differenceObject -PassThru
        foreach ($diff in $delta)
        {
            if ($diff.SideIndicator -eq '=>')
            {
                $category = $Categories | Where-Object -Property DisplayName -EQ $diff
                if ($category.Id)
                {
                    $currentCategory = Get-MgBetaDeviceAppManagementMobileAppCategory -MobileAppCategoryId $category.Id -ErrorAction SilentlyContinue
                }

                if ($null -eq $currentCategory)
                {
                    $currentCategory = Get-MgBetaDeviceAppManagementMobileAppCategory -Filter "DisplayName eq '$($category.DisplayName -replace "'", "''")'"
                }

                if ($null -eq $currentCategory -or $currentCategory.Count -eq 0)
                {
                    throw "Mobile App Category with DisplayName $($category.DisplayName) not found."
                }

                Invoke-MgGraphRequest -Uri "/beta/deviceAppManagement/mobileApps/$($App.Id)/categories/`$ref" -Method 'POST' -Body @{
                    '@odata.id' = "$((Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl)beta/deviceAppManagement/mobileAppCategories/$($currentCategory.Id)"
                }
            }
            else
            {
                $category = $App.Categories | Where-Object -Property DisplayName -EQ $diff
                Invoke-MgGraphRequest -Uri "/beta/deviceAppManagement/mobileApps/$($App.Id)/categories/$($category.Id)/`$ref" -Method 'DELETE'
            }
        }
    }
    else
    {
        foreach ($category in $Categories)
        {
            if ($category.Id)
            {
                $currentCategory = Get-MgBetaDeviceAppManagementMobileAppCategory -MobileAppCategoryId $category.Id -ErrorAction SilentlyContinue
            }

            if ($null -eq $currentCategory)
            {
                $currentCategory = Get-MgBetaDeviceAppManagementMobileAppCategory -Filter "DisplayName eq '$($category.DisplayName -replace "'", "''")'"
            }

            if ($null -eq $currentCategory -or $currentCategory.Count -eq 0)
            {
                throw "Mobile App Category with DisplayName $($category.DisplayName) not found."
            }

            Invoke-MgGraphRequest -Uri "$((Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl)beta/deviceAppManagement/mobileApps/$($App.Id)/categories/`$ref" -Method 'POST' -Body @{
                '@odata.id' = "$((Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl)beta/deviceAppManagement/mobileAppCategories/$($currentCategory.Id)"
            }
        }
    }
}

<#
.SYNOPSIS
    Builds setting instances for an Intune configuration template.
 
.DESCRIPTION
    Retrieves recommended setting definitions for a template category and merges them with provided property values.
    The function outputs settings in the format expected by configuration policy update calls.
 
.PARAMETER Properties
    Specifies desired property values keyed by setting suffix.
 
.PARAMETER TemplateId
    Specifies the Intune configuration template identifier.
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Get-M365DSCIntuneDeviceConfigurationSettings
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = 'true')]
        [System.Collections.Hashtable]
        $Properties,

        [Parameter()]
        [System.String]
        $TemplateId
    )

    $templateCategoryId = (Get-MgBetaDeviceManagementTemplateCategory -DeviceManagementTemplateId $TemplateId).Id
    $templateSettings = Get-MgBetaDeviceManagementTemplateCategoryRecommendedSetting `
        -DeviceManagementTemplateId $TemplateId `
        -DeviceManagementTemplateSettingCategoryId $templateCategoryId

    $results = @()
    foreach ($setting in $templateSettings)
    {
        $result = @{}
        $settingType = $setting.'@odata.type'
        $settingValue = $null
        $currentValueKey = $Properties.keys | Where-Object -FilterScript { $setting.DefinitionId -like "*$_" }
        if ($null -ne $currentValueKey)
        {
            $settingValue = $Properties.$currentValueKey
        }

        $requiresValueJson = $false
        switch ($settingType)
        {
            {
                ( $_ -eq '#microsoft.graph.deviceManagementStringSettingInstance' ) -or
                ( $_ -eq '#microsoft.graph.deviceManagementBooleanSettingInstance' )
            }
            {
                if ([String]::IsNullOrEmpty($settingValue))
                {
                    $settingValue = $setting.ValueJson | ConvertFrom-Json
                }
            }
            '#microsoft.graph.deviceManagementCollectionSettingInstance'
            {
                $requiresValueJson = $true
                if ($null -eq $settingValue)
                {
                    $settingValue = ConvertTo-Json -InputObject @()
                }
                else
                {
                    $settingValue = ConvertTo-Json -InputObject ([Array]$settingValue)
                }
            }
            default
            {
                if ($null -eq $settingValue)
                {
                    $settingValue = $setting.ValueJson | ConvertFrom-Json
                }
            }
        }

        $result.Add('@odata.type', $settingType)
        $result.Add('Id', $setting.Id)
        $result.Add('definitionId', $setting.DefinitionId)

        if ($requiresValueJson)
        {
            $result.Add('valueJson', $settingValue)
        }
        else
        {
            $result.Add('value', $settingValue)
        }

        $results += $result
    }

    return $results
}

<#
.SYNOPSIS
    Retrieves the decrypted value for an OMA setting secret reference.
 
.DESCRIPTION
    Extracts the parent policy identifier from a secret reference value id and calls Graph to return the plain-text OMA setting value.
    Returns null when the identifier is invalid or decryption fails.
 
.PARAMETER SecretReferenceValueId
    Specifies the secret reference identifier associated with an encrypted OMA setting value.
 
.PARAMETER APIVersion
    Specifies the Graph API version to call.
 
.OUTPUTS
    System.String
#>

function Get-OmaSettingPlainTextValue
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Mandatory = $true)]
        [System.String]
        $SecretReferenceValueId,

        [Parameter()]
        [ValidateSet('v1.0', 'beta')]
        [System.String]
        $APIVersion = 'beta'
    )

    try
    {
        <#
            e.g. PolicyId for SecretReferenceValueId '35ea58ec-2a79-471d-8eea-7e28e6cd2722_bdf6c690-05fb-4d02-835d-5a7406c35d58_abe32712-2255-445f-a35e-0c6f143d82ca'
            is 'bdf6c690-05fb-4d02-835d-5a7406c35d58'
        #>

        $SplitSecretReferenceValueId = $SecretReferenceValueId.Split('_')
        if ($SplitSecretReferenceValueId.Count -eq 3)
        {
            $PolicyId = $SplitSecretReferenceValueId[1]
        }
        else
        {
            return $null
        }
    }
    catch
    {
        return $null
    }

    $Repository = 'deviceManagement/deviceConfigurations'
    $Uri = "/{0}/{1}/{2}/getOmaSettingPlainTextValue(secretReferenceValueId='{3}')" -f $APIVersion, $Repository, $PolicyId, $SecretReferenceValueId

    try
    {
        $Result = Invoke-MgGraphRequest -Method GET -Uri $Uri -ErrorAction Stop
    }
    catch
    {
        $Message = 'Error decrypting OmaSetting with SecretReferenceValueId {0}:' -f $SecretReferenceValueId
        New-M365DSCLogEntry -Message $Message `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return $null
    }

    if (![String]::IsNullOrEmpty($Result.Value))
    {
        return $Result.Value
    }
    else
    {
        return $null
    }
}

<#
.SYNOPSIS
    Constructs setting catalog policy settings from DSC parameters.
 
.DESCRIPTION
    Builds setting catalog policy setting objects from DSC input values.
    It can start from a template id or use preloaded setting templates for nested calls.
 
.PARAMETER DSCParams
    Specifies DSC parameter values used to build setting instances.
 
.PARAMETER TemplateId
    Specifies the setting catalog template identifier used in the Start parameter set.
 
.PARAMETER SettingTemplates
    Specifies preloaded setting templates used in the DeviceAndUserSettings parameter set.
 
.PARAMETER ContainsDeviceAndUserSettings
    Indicates whether the policy contains both device and user settings.
 
.OUTPUTS
    System.Object[]
#>

function Get-IntuneSettingCatalogPolicySetting
{
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $DSCParams,

        [Parameter(
            Mandatory = 'true',
            ParameterSetName = 'Start'
        )]
        [System.String]
        $TemplateId,

        [Parameter(
            Mandatory = 'true',
            ParameterSetName = 'DeviceAndUserSettings'
        )]
        [System.Array]
        $SettingTemplates,

        [Parameter(ParameterSetName = 'Start')]
        [switch]
        $ContainsDeviceAndUserSettings
    )

    $DSCParams.Remove('Identity') | Out-Null
    $DSCParams.Remove('DisplayName') | Out-Null
    $DSCParams.Remove('Description') | Out-Null

    if ($PSCmdlet.ParameterSetName -eq 'Start')
    {
        # Prepare setting definitions mapping
        $SettingTemplates = Get-MgBetaDeviceManagementConfigurationPolicyTemplateSettingTemplate `
            -DeviceManagementConfigurationPolicyTemplateId $TemplateId `
            -ExpandProperty 'SettingDefinitions' `
            -All
    }

    Initialize-M365DSCDllLoader -ErrorAction Stop

    return ,[Microsoft365DSC.Intune.SettingCatalogPolicySettingBuilder]::Build(
        [System.Collections.Generic.List[object]]@($SettingTemplates),
        $DSCParams,
        $ContainsDeviceAndUserSettings.IsPresent)
}

<#
.SYNOPSIS
    Exports Intune setting catalog settings into DSC hashtable format.
 
.DESCRIPTION
    Converts setting catalog instances from Graph into the return hashtable used by export routines.
    It supports root export execution and nested setting processing flows.
 
.PARAMETER Settings
    Specifies the settings collection to export in the Start parameter set.
 
.PARAMETER ReturnHashtable
    Specifies the hashtable that receives exported values.
 
.PARAMETER SettingInstance
    Specifies the current setting instance to process in the Setting parameter set.
 
.PARAMETER SettingDefinitions
    Specifies setting definitions used when processing a single setting instance.
 
.PARAMETER AllSettingDefinitions
    Specifies all available setting definitions for name and value resolution.
 
.PARAMETER IsRoot
    Indicates that the current setting instance is processed as a root node.
 
.PARAMETER ContainsDeviceAndUserSettings
    Indicates whether the policy contains both device and user settings.
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Export-IntuneSettingCatalogPolicySettings
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param(
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Start'
        )]
        $Settings,

        [Parameter(
            Mandatory = $true
        )]
        [System.Collections.Hashtable]$ReturnHashtable,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Setting'
        )]
        $SettingInstance,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Setting'
        )]
        $SettingDefinitions,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Setting'
        )]
        [Parameter(
            ParameterSetName = 'Start'
        )]
        [System.Array]
        $AllSettingDefinitions,

        [Parameter(
            ParameterSetName = 'Setting'
        )]
        [switch]$IsRoot,

        [Parameter(
            ParameterSetName = 'Start'
        )]
        [switch]$ContainsDeviceAndUserSettings
    )

    Initialize-M365DSCDllLoader -ErrorAction Stop

    return [Microsoft365DSC.Intune.SettingCatalogPolicyExporter]::Export($Settings, $ReturnHashtable, $AllSettingDefinitions, $ContainsDeviceAndUserSettings)
}

<#
.SYNOPSIS
    Updates an Intune configuration policy definition.
 
.DESCRIPTION
    Builds a policy update payload and sends it to the Intune configuration policy endpoint.
    Optional template and creation source values are included only when supplied.
 
.PARAMETER DeviceConfigurationPolicyId
    Specifies the identifier of the configuration policy to update.
 
.PARAMETER Name
    Specifies the policy display name.
 
.PARAMETER Description
    Specifies the policy description.
 
.PARAMETER Platforms
    Specifies the target platform value for the policy.
 
.PARAMETER Technologies
    Specifies the technologies value for the policy.
 
.PARAMETER TemplateReferenceId
    Specifies the template identifier to reference in the policy payload.
 
.PARAMETER CreationSource
    Specifies the creation source value to include in the payload.
 
.PARAMETER Settings
    Specifies the settings collection to apply to the policy.
 
.PARAMETER RoleScopeTagIds
    Specifies role scope tag identifiers assigned to the policy.
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Update-IntuneDeviceConfigurationPolicy
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = 'true')]
        [System.String]
        $DeviceConfigurationPolicyId,

        [Parameter()]
        [System.String]
        $Name,

        [Parameter()]
        [System.String]
        $Description,

        [Parameter()]
        [System.String]
        $Platforms,

        [Parameter()]
        [System.String]
        $Technologies,

        [Parameter()]
        [System.String]
        $TemplateReferenceId,

        [Parameter()]
        [AllowNull()]
        [System.String]
        $CreationSource,

        [Parameter()]
        [Array]
        $Settings,

        [Parameter()]
        [System.String[]]
        $RoleScopeTagIds
    )

    try
    {
        $Uri = (Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl + "beta/deviceManagement/configurationPolicies/$DeviceConfigurationPolicyId"

        $policy = @{
            'name'            = $Name
            'description'     = $Description
            'platforms'       = $Platforms
            'technologies'    = $Technologies
            'settings'        = $Settings
            'roleScopeTagIds' = $RoleScopeTagIds
        }

        if ($PSBoundParameters.ContainsKey('TemplateReferenceId'))
        {
            $policy.Add('templateReference', @{ 'templateId' = $TemplateReferenceId })
        }

        if ($PSBoundParameters.ContainsKey('CreationSource') -and -not [System.String]::IsNullOrEmpty($CreationSource))
        {
            $policy.Add('creationSource', $CreationSource)
        }

        $body = $policy | ConvertTo-Json -Depth 20
        Write-Verbose -Message "Updating policy with:`r`n$body"
        Invoke-MgGraphRequest -Method PUT -Uri $Uri -Body $body -ErrorAction Stop
    }
    catch
    {
        New-M365DSCLogEntry -Message 'Error updating data:' `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        throw
    }
}

<#
.SYNOPSIS
    Extracts startswith, endswith, and contains clauses from a filter query.
 
.DESCRIPTION
    Parses an OData-like filter string and returns complex function fragments.
    These fragments can then be evaluated against in-memory policy objects.
 
.PARAMETER FilterQuery
    Specifies the filter query string to parse.
 
.OUTPUTS
    System.Array
#>

function Get-ComplexFunctionsFromFilterQuery
{
    [CmdletBinding()]
    [OutputType([System.Array])]
    param (
        [string]$FilterQuery
    )

    $complexFunctionsRegex = "startswith\((.*?),\s*'(.*?)'\)|endswith\((.*?),\s*'(.*?)'\)|contains\((.*?),\s*'(.*?)'\)"
    [array]$complexFunctions = [regex]::Matches($FilterQuery, $complexFunctionsRegex) | ForEach-Object {
        $_.Value
    }

    return $complexFunctions
}

<#
.SYNOPSIS
    Removes complex function clauses from a filter query.
 
.DESCRIPTION
    Removes startswith, endswith, and contains clauses from an OData-like filter string.
    The resulting query can be sent to Graph while complex predicates are evaluated locally.
 
.PARAMETER FilterQuery
    Specifies the filter query string to simplify.
 
.OUTPUTS
    System.String
#>

function Remove-ComplexFunctionsFromFilterQuery
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [string]$FilterQuery
    )

    $complexFunctionsRegex = "startswith\((.*?),\s*'(.*?)'\)|endswith\((.*?),\s*'(.*?)'\)|contains\((.*?),\s*'(.*?)'\)"
    $basicFilterQuery = [regex]::Replace($FilterQuery, $complexFunctionsRegex, '').Trim()
    $basicFilterQuery = $basicFilterQuery -replace '^and\s', '' -replace '\sand$', '' -replace '\sand\s+', ' and ' -replace '\sor\s+', ' or '

    return $basicFilterQuery
}

<#
.SYNOPSIS
    Applies complex filter function fragments to a policy collection.
 
.DESCRIPTION
    Evaluates startswith, endswith, and contains predicates against policy properties.
    Returns only policies that satisfy all provided complex function conditions.
 
.PARAMETER Policies
    Specifies the policy collection to filter.
 
.PARAMETER ComplexFunctions
    Specifies parsed complex function expressions to apply.
 
.OUTPUTS
    System.Array
#>

function Find-GraphDataUsingComplexFunctions
{
    [CmdletBinding()]
    [OutputType([System.Array])]
    param (
        [array]$Policies,
        [array]$ComplexFunctions
    )

    foreach ($function in $ComplexFunctions)
    {
        if ($function -match "startswith\((.*?),\s*'(.*?)'")
        {
            $property = $matches[1]
            $value = $matches[2]
            $Policies = $Policies | Where-Object -Property $property -Like "$value*"
        }
        elseif ($function -match "endswith\((.*?),\s*'(.*?)'")
        {
            $property = $matches[1]
            $value = $matches[2]
            $Policies = $Policies | Where-Object -Property $property -Like "*$value"
        }
        elseif ($function -match "contains\((.*?),\s*'(.*?)'")
        {
            $property = $matches[1]
            $value = $matches[2]
            $Policies = $Policies | Where-Object -Property $property -Like "*$value*"
        }
    }

    return $Policies
}

<#
.SYNOPSIS
    Performs the initial content upload flow for an Intune mobile app.
 
.DESCRIPTION
    Creates a content version and placeholder file, uploads sample encrypted content to Azure Storage, commits the file, and updates the app with the committed content version.
    This helper supports app creation flows that require an initial content artifact.
 
.PARAMETER AppId
    Specifies the target mobile app identifier.
 
.PARAMETER OdataType
    Specifies the mobile app OData type used for content endpoints.
 
.PARAMETER FileExtension
    Specifies the file extension used when creating the sample content file.
#>

function Invoke-M365DSCIntuneMobileAppInitialUpload
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $AppId,

        [Parameter(Mandatory = $true)]
        [System.String]
        $OdataType,

        [Parameter(Mandatory = $true)]
        [System.String]
        $FileExtension
    )

    $OdataType = $OdataType.Replace('#', '')
    $contentVersionsUri = "beta/deviceAppManagement/mobileApps/$($AppId)/$OdataType/contentVersions"
    $contentVersion = Invoke-MgGraphRequest -Method POST -Uri $contentVersionsUri -Body @{}

    $manifest = $null
    $size = 1
    $sizeEncrypted = 64
    $base64File = '+drh1SKfuLjdp37gfv8EuWqOTt06m0TirqJJ0xQvrd5sm6NkiYBY8vBkFM+9ZwHRskO83NEfsLPtTzLB9FFsKA=='
    $encryptionKey = 'yqjlzT5KYpwU0wkr5eJGGukMB0Ar8iGqYX3B0lJJnKk='
    $fileDigest = 'ypeBEsobvcr6wjGzmiPcTaeG7/gUfE5yuYB3ha/uSLs='
    $initializationVector = 'bJujZImAWPLwZBTPvWcB0Q=='
    $mac = '+drh1SKfuLjdp37gfv8EuWqOTt06m0TirqJJ0xQvrd4='
    $macKey = 'mGfhTn/0AB3fftWzENQcoU34xghAfvVq23PoiBD81tM='
    switch ($OdataType)
    {
        'microsoft.graph.androidLobApp'
        {
            $size = 13168
            $sizeEncrypted = 13232
            $manifest = $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('<?xml version="1.0" encoding="utf-8"?><AndroidManifestProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Package>a.a</Package><PackageVersionCode>1</PackageVersionCode><PackageVersionName>1.0</PackageVersionName><ApplicationName>Sample.apk</ApplicationName><MinSdkVersion>24</MinSdkVersion><AWTVersion></AWTVersion></AndroidManifestProperties>')))
            $base64File = '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'
            $encryptionKey = '3uV+NpJnmT5vaufsl9xalZJqwpFmACdOT1OQH9LBJNQ='
            $fileDigest = 'EVykpzyjw9oNRwY4NR/JJNC7rjgSauvcOcuQ/hjHQu0='
            $initializationVector = 'd/kzcmL9UjjMlPxQdPIA6g=='
            $mac = 'l+rn68BUBB1/+Od1k1JnMcqcbzrbf7vhcy+zofWz6YY='
            $macKey = 'kb7oEqtP0SJGiy6jrkj0rUggcCmkcb3AkFkXTYovnLc='
        }
        'microsoft.graph.windowsMobileMSI'
        {
            $manifest = $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('<MobileMsiData MsiExecutionContext="User" MsiRequiresReboot="false" MsiUpgradeCode="{00000000-0000-0000-0000-000000000000}" MsiIsMachineInstall="false" MsiIsUserInstall="true" MsiRequiresLogon="true" MsiIncludesServices="false" MsiContainsSystemRegistryKeys="false" MsiContainsSystemFolders="false"></MobileMsiData>')))
        }
        'microsoft.graph.windowsUniversalAppx'
        {
            $manifest = $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("Sample.$($FileExtension)".ToUpper())))
        }
    }

    $fileUri = "beta/deviceAppManagement/mobileApps/$($AppId)/$OdataType/contentVersions/$($contentVersion.id)/files"
    if ($OdataType -eq 'microsoft.graph.windowsUniversalAppx')
    {
        # Manifest is required for Windows Universal Appx
        $manifest = $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("Sample.$($FileExtension)".ToUpper())))
    }
    elseif ($OdataType -eq 'microsoft.graph.windowsMobileMSI')
    {
        # Manifest is required for Windows Mobile MSI
        $manifest = $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('<MobileMsiData MsiExecutionContext="User" MsiRequiresReboot="false" MsiUpgradeCode="{00000000-0000-0000-0000-000000000000}" MsiIsMachineInstall="false" MsiIsUserInstall="true" MsiRequiresLogon="true" MsiIncludesServices="false" MsiContainsSystemRegistryKeys="false" MsiContainsSystemFolders="false"></MobileMsiData>')))
    }
    $file = Invoke-MgGraphRequest -Method POST `
        -Uri $fileUri `
        -Body @{
            '@odata.type' = '#microsoft.graph.mobileAppContentFile'
            name          = "Sample.$($FileExtension)"
            size          = $size
            sizeEncrypted = $sizeEncrypted
            isDependency  = $false
            manifest      = $manifest
        }

    $file = Wait-ForFileProcessing -AppId $AppId -OdataType $OdataType -FileId $file.id -ContentVersionId $contentVersion.id -UploadStatePrefix 'AzureStorageUriRequest'

    # Upload the encrypted Sample file to Azure Storage
    $success = $false
    $breakCounter = 0
    do
    {
        try
        {
            Write-Verbose "Uploading file to Azure Storage: $($file.azureStorageUri)"
            $sasUri = $file.azureStorageUri
            $uri = "$($sasUri)&comp=block&blockid=0001"
            $iso = [System.Text.Encoding]::GetEncoding('iso-8859-1')
            $body = [System.Convert]::FromBase64String($base64File)
            $encodedBody = $iso.GetString($body)
            Invoke-WebRequest -Uri $uri -Method PUT -Body $encodedBody -Headers @{
                'content-type' = 'text/plain; charset=iso-8859-1'
                'x-ms-blob-type' = 'BlockBlob'
            } -ErrorAction Stop -UseBasicParsing | Out-Null
            Write-Verbose 'File uploaded successfully to Azure Storage.'
            $success = $true
        }
        catch
        {
            Write-Warning -Message "Failed to upload file to Azure Storage: $($_.Exception.Message)"
            Start-Sleep -Seconds 2
        }
    } while ($success -eq $false -and $breakCounter -lt 5)

    # Finalize the upload to Azure Storage
    $uri = "$($sasUri)&comp=blocklist"
    $xml = '<?xml version="1.0" encoding="utf-8"?><BlockList><Latest>0001</Latest></BlockList>'
    Invoke-RestMethod -Uri $uri -Method PUT -Body $xml

    # Commit the file and update the app
    $jsonCommit = @{
        fileEncryptionInfo = @{
            fileDigestAlgorithm  = 'SHA256'
            encryptionKey        = $encryptionKey
            initializationVector = $initializationVector
            fileDigest           = $fileDigest
            mac                  = $mac
            profileIdentifier    = 'ProfileVersion1'
            macKey               = $macKey
        }
    }
    $commitUri = "beta/deviceAppManagement/mobileApps/$AppId/$OdataType/contentVersions/$($contentVersion.id)/files/$($file.id)/commit"
    Invoke-MgGraphRequest -Method POST -Uri $commitUri -Body $($jsonCommit | ConvertTo-Json -Depth 10)

    Wait-ForFileProcessing -AppId $AppId -OdataType $OdataType -FileId $file.id -ContentVersionId $contentVersion.id -UploadStatePrefix 'CommitFile'

    # Update the app with the committed content version
    Invoke-MgGraphRequest -Method PATCH -Uri "beta/deviceAppManagement/mobileApps/$AppId" -Body @{
        '@odata.type'           = "#$OdataType"
        committedContentVersion = '1'
    }
}

<#
.SYNOPSIS
    Waits until Intune mobile app content file processing completes.
 
.DESCRIPTION
    Polls the mobile app content file endpoint until the upload state reaches the expected success state.
    Throws when the upload state enters a failure state.
 
.PARAMETER AppId
    Specifies the target mobile app identifier.
 
.PARAMETER OdataType
    Specifies the mobile app OData type used for the file endpoint.
 
.PARAMETER FileId
    Specifies the content file identifier.
 
.PARAMETER ContentVersionId
    Specifies the content version identifier containing the file.
 
.PARAMETER UploadStatePrefix
    Specifies the upload state prefix that is combined with Success for completion checks.
 
.OUTPUTS
    System.Object
#>

function Wait-ForFileProcessing
{
    [CmdletBinding()]
    [OutputType([System.Object])]
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $AppId,

        [Parameter(Mandatory = $true)]
        [System.String]
        $OdataType,

        [Parameter(Mandatory = $true)]
        [System.String]
        $FileId,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ContentVersionId,

        [Parameter(Mandatory = $true)]
        [System.String]
        $UploadStatePrefix
    )

    $fileUri = "beta/deviceAppManagement/mobileApps/$($AppId)/$OdataType/contentVersions/$ContentVersionId/files/$($FileId)"

    Write-Verbose "Waiting for file processing to complete for AppId: $AppId, OdataType: $OdataType, FileId: $FileId, ContentVersionId: $ContentVersionId"
    $file = Invoke-MgGraphRequest -Method GET -Uri $fileUri

    while ($file.uploadState -ne "$($UploadStatePrefix)Success")
    {
        if ($file.uploadState -like '*Failed')
        {
            throw "File upload failed with state: $($file.uploadState). Please check the file and try again."
        }

        Start-Sleep -Seconds 1
        Write-Verbose "Current upload state: $($file.uploadState). Waiting for processing to complete..."
        $file = Invoke-MgGraphRequest -Method GET -Uri $fileUri
    }

    $file
}

Export-ModuleMember -Function @(
    'Compare-M365DSCIntunePolicyAssignment',
    'ConvertFrom-IntuneMobileAppAssignment',
    'ConvertFrom-IntunePolicyAssignment',
    'ConvertTo-IntuneMobileAppAssignment',
    'ConvertTo-IntunePolicyAssignment',
    'Export-IntuneSettingCatalogPolicySettings',
    'Find-GraphDataUsingComplexFunctions',
    'Get-ComplexFunctionsFromFilterQuery',
    'Get-IntuneSettingCatalogPolicySetting',
    'Get-M365DSCExportCachedConfigurationPolicies',
    'Get-M365DSCIntuneDeviceConfigurationSettings',
    'Get-OmaSettingPlainTextValue',
    'Invoke-M365DSCIntuneMobileAppInitialUpload',
    'Remove-ComplexFunctionsFromFilterQuery',
    'Update-DeviceAppManagementAppCategory',
    'Update-DeviceAppManagementPolicyAssignment',
    'Update-DeviceConfigurationPolicyAssignment',
    'Update-IntuneDeviceConfigurationPolicy',
    'Wait-ForFileProcessing'
)