MSEndpointMgr / IntuneWin32App

Provides a set of functions to manage all aspects of Win32 apps in Microsoft Intune.
MIT License
345 stars 88 forks source link

Add-IntuneWin32AppAssignmentGroup Exclude from uninstall not working if the same group is included in install #76

Open GaborDienesLego opened 1 year ago

GaborDienesLego commented 1 year ago

Hi,

We would like to exclude the required and available groups from uninstall (so we can add all users to uninstall, and we won't remove the app from the selected groups), but it's not possible as the group is already assigned to the app. On the console, it is possible to do. The function is not checking the intent of the assignment, only its existence. The error message: WARNING: Win32 app assignment with id 'xxxx' of target type 'Group' and GroupID 'yyyyy' already exists, duplicate assignments of this type is not permitted

Thank you! Gabor

gbdixg commented 1 year ago

Hi, I'm also affected by this, but slightly differently.

I have separate required, available and uninstall groups assigned to the app for "included". I'm then trying to add the Uninstall group as an exclusion for Required and Available assignments, which is possible in the admin center.

"Test-IntuneWin32AppAssignment" function does not take account of Include vs Exclude when checking existing group assignment and incorrectly returns the warning 'duplicate assignments of this type is not permitted'.

GaborDienesLego commented 1 year ago

Yes, it's the test function that makes it fail to add. Based on the module I wrote a function to exclude a group from uninstall.

Function Exclude-UninstallAssignmentGroup { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$AppilcationID,

    [parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$GroupID        
)
# Ensure required authentication header variable exists
    if ($Global:AuthenticationHeader -eq $null) {
        Write-Warning -Message "Authentication token was not found, use Connect-MSIntuneGraph before using this function"; break
        return
    }
    else {
        $TokenLifeTime = ($Global:AuthenticationHeader.ExpiresOn - (Get-Date).ToUniversalTime()).Minutes
        if ($TokenLifeTime -le 0) {
            Write-Warning -Message "Existing token found but has expired, use Connect-MSIntuneGraph to request a new authentication token"; break
            return
        }
        else {
            Write-Verbose -Message "Current authentication token expires in (minutes): $($TokenLifeTime)"
        }
    }

Write-Verbose -Message "Querying for Win32 app using ID: $($AppilcationID)"

# Checking if the app exists
$Resource   = "mobileApps/$($AppilcationID)"
$GraphURI   = "https://graph.microsoft.com/Beta/deviceAppManagement/$($Resource)"
$Win32App = Invoke-RestMethod -Uri $GraphURI -Headers $Global:AuthenticationHeader -Method GET -ErrorAction Stop -Verbose:$false

# If the app exists...
if ($Win32App -ne $null) {
    $Win32AppID = $AppilcationID
    # Construct target assignment body

    $DataType = "#microsoft.graph.exclusionGroupAssignmentTarget"
    $TargetAssignment = @{
        "@odata.type" = $DataType
        "deviceAndAppManagementAssignmentFilterId" = $null
        "deviceAndAppManagementAssignmentFilterType" = "none"
        "groupId" = $GroupID
    }

    # Construct table for Win32 app assignment body
    $Win32AppAssignmentBody = [ordered]@{
        "@odata.type" = "#microsoft.graph.mobileAppAssignment"
        "intent" = "uninstall"
        "source" = "direct"
        "target" = $TargetAssignment
    }

    $Win32AppAssignmentBody.Add("settings", $null)

    try {
        $Resource   = "mobileApps/$($AppilcationID)"
        $GraphURI = "https://graph.microsoft.com/Beta/deviceAppManagement/mobileApps/$($Win32AppID)/assignments"

        $Win32AppAssignmentResponse = Invoke-RestMethod -Uri $GraphURI -Headers $Global:AuthenticationHeader -Method POST -Body ($Win32AppAssignmentBody | ConvertTo-Json) -ContentType "application/json" -ErrorAction Stop -Verbose:$false

        if ($Win32AppAssignmentResponse.id) {
            Write-Verbose -Message "Successfully created Win32 app assignment with ID: $($Win32AppAssignmentResponse.id)"
            Write-Output -InputObject $Win32AppAssignmentResponse
            }
        }
    catch [System.Exception] {
                Write-Warning -Message "An error occurred while creating a Win32 app assignment. Error message: $($_.Exception.Message)"
        }

}
else {
        Write-Warning -Message "No apps with ID '$($AppilcationID)' was found"
    }

}

Example of calling it: Exclude-UninstallAssignmentGroup -AppilcationID $intuneAppID -GroupID $GroupID

emipa606 commented 1 year ago

Created an issue about this but noticed this and will add relevant info here instead:

The following setup is a valid use-case that is blocked by this test:

In the above the last step is blocked as there already is an assignment on the Uninstall - Application group. However, the exclusion of this group is to not show the application as available for the members of the forced uninstall-group. This is possible to do in the web-gui as well. As the Company Portal does not uninstall applications that is listed as available for a user, this exclusion is the only way to be sure that the application is correctly uninstalled when added to Uninstall - Application

Another use-case where having multiple assignments on the same group is valid.

This would be to be sure that noone can get both the install and uninstall-assignment a the same time.

-- For those that have these issues I solved it locally by overriding the Test-IntuneWin32AppAssignment with an Alias that just returns false until this is fixed:

function OverrideFalse {
    return $false
}
Import-Module IntuneWin32App
New-Alias -Name 'Test-IntuneWin32AppAssignment' -Value 'OverrideFalse' -Scope Global
ChrisStecher commented 1 year ago

A quick fix if you wanna change it for the next update, could be to just add another check in the Test-IntuneWin32AppAssignment in line 62-72:

From:

                switch ($Target) {
                    "Group" {
                        foreach ($Win32AppAssignment in $Win32AppAssignments.value) {
                            if ($Win32AppAssignment.target.'@odata.type' -match "groupAssignmentTarget") {
                                if ($Win32AppAssignment.target.groupId -like $GroupID) {
                                    Write-Warning -Message "Win32 app assignment with id '$($Win32AppAssignment.id)' of target type '$($Target)' and GroupID '$($Win32AppAssignment.target.groupId)' already exists, duplicate assignments of this type is not permitted"
                                    $DuplicateAssignmentDetected = $true
                                }
                            }
                        }
                    }

to:

                switch ($Target) {
                    "Group" {
                        foreach ($Win32AppAssignment in $Win32AppAssignments.value) {
                            if ($Win32AppAssignment.target.'@odata.type' -match "groupAssignmentTarget") {
                                if ($Win32AppAssignment.target.groupId -like $GroupID -and $Win32AppAssignment.Intent -like $Intent) {
                                    Write-Warning -Message "Win32 app assignment with id '$($Win32AppAssignment.id)' of target type '$($Target)' and GroupID '$($Win32AppAssignment.target.groupId)' already exists, duplicate assignments of this type is not permitted"
                                    $DuplicateAssignmentDetected = $true
                                }
                            }
                        }
                    }