ScriptBlocks/Grant-Access-DriveItemPermission.ps1
|
param( [Parameter(Mandatory=$true)] [ValidateSet('grant','read','delete')] [string]$Mode, [Parameter(Mandatory=$true)] [string]$ClientId, [Parameter(Mandatory=$true)] [string]$TenantId, [Parameter(Mandatory=$true)] [string]$Thumbprint, [Parameter(Mandatory=$true)] [string]$TenantHost, [Parameter(Mandatory=$true)] [string]$SitePath, [Parameter(Mandatory=$true)] [string]$ContainerName, [Parameter(Mandatory=$true)] [AllowEmptyString()] [string]$FolderPath, [Parameter(Mandatory=$false)] [AllowEmptyString()] [string]$FilePath = '', [Parameter(Mandatory=$true)] [string]$TargetAppId, [ValidateSet('read','write')] [string]$Role = 'read', [switch]$ForceUpgrade, [switch]$CleanDuplicates, [switch]$AllowRoot ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $VerbosePreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' $InformationPreference = 'SilentlyContinue' function _RoleRank([string[]]$roles) { $r = @($roles) if ($r -contains 'owner') { return 3 } if ($r -contains 'write') { return 2 } if ($r -contains 'read') { return 1 } return 0 } function _Satisfies([string[]]$existingRoles, [string]$requestedRole) { $r = @($existingRoles) if ($requestedRole -eq 'read') { return ($r -contains 'read' -or $r -contains 'write' -or $r -contains 'owner') } if ($requestedRole -eq 'write') { return ($r -contains 'write' -or $r -contains 'owner') } return $false } function _GetAppPermissionMatches($permissions, [string]$appId) { $pmatches = @() foreach ($p in @($permissions)) { if ($p.grantedToV2 -and $p.grantedToV2.application -and $p.grantedToV2.application.id -eq $appId) { $pmatches += $p continue } if ($p.grantedToIdentitiesV2) { foreach ($idset in @($p.grantedToIdentitiesV2)) { if ($idset.application -and $idset.application.id -eq $appId) { $pmatches += $p break } } } } return $pmatches } function Convert-ToGraphDrivePath { param( [Parameter(Mandatory=$true)] [AllowEmptyString()] [string]$Path ) if ([string]::IsNullOrWhiteSpace($Path)) { return '' } $segments = @( ($Path.Trim('/') -split '/') | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { [System.Uri]::EscapeDataString($_) } ) return ($segments -join '/') } function Join-GraphDriveRelativePath { param( [Parameter(Mandatory=$false)] [AllowEmptyString()] [string]$FolderPath, [Parameter(Mandatory=$false)] [AllowEmptyString()] [string]$FilePath ) $parts = New-Object System.Collections.Generic.List[string] if (-not [string]::IsNullOrWhiteSpace($FolderPath)) { [void]$parts.Add($FolderPath.Trim('/')) } if (-not [string]::IsNullOrWhiteSpace($FilePath)) { [void]$parts.Add($FilePath.Trim('/')) } if ($parts.Count -eq 0) { return '' } return (($parts.ToArray()) -join '/') } function Resolve-DriveIdFromContainerName { param( [Parameter(Mandatory=$true)][string]$SiteId, [Parameter(Mandatory=$true)][string]$ContainerName ) $drivesUri = 'https://graph.microsoft.com/v1.0/sites/{0}/drives?`$select=id,name,webUrl' -f $SiteId $drives = Invoke-MgGraphRequest -Method GET -Uri $drivesUri $driveList = @($drives.value) $drive = $driveList | Where-Object { $_.name -eq $ContainerName } | Select-Object -First 1 if (-not $drive) { $pattern = '/{0}(/|$)' -f [regex]::Escape($ContainerName) $drive = $driveList | Where-Object { $_.webUrl -and ([System.Net.WebUtility]::UrlDecode([string]$_.webUrl) -match $pattern) } | Select-Object -First 1 } if (-not $drive) { $listsUri = 'https://graph.microsoft.com/v1.0/sites/{0}/lists?`$select=id,displayName,webUrl' -f $SiteId $lists = Invoke-MgGraphRequest -Method GET -Uri $listsUri $list = @($lists.value) | Where-Object { $_.displayName -eq $ContainerName } | Select-Object -First 1 if ($list -and $list.webUrl) { $listUrlDecoded = [System.Net.WebUtility]::UrlDecode([string]$list.webUrl) $drive = $driveList | Where-Object { $_.webUrl -and ([System.Net.WebUtility]::UrlDecode([string]$_.webUrl) -eq $listUrlDecoded) } | Select-Object -First 1 } } if (-not $drive) { $available = ($driveList | ForEach-Object { '{0} => {1}' -f $_.name, $_.webUrl }) -join '; ' throw ('Drive (library) ''{0}'' not found on site. Available drives: {1}' -f $ContainerName, $available) } return $drive.id } function Test-WorkerAppPermissions_ExtendedWithDelegated { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$WorkerAppId, [Parameter()] [string[]]$RequiredGraphAppRoles = @(), [Parameter()] [string[]]$RequiredGraphDelegatedScopes = @(), [Parameter()] [string[]]$RequiredSharePointAppRoles = @(), [Parameter()] [string[]]$RequiredSharePointDelegatedScopes = @() ) $MicrosoftGraphAppId = '00000003-0000-0000-c000-000000000000' $SharePointOnlineAppId = '00000003-0000-0ff1-ce00-000000000000' $SupportedGraphSelectedPermissions = @( 'Sites.Selected' 'Lists.SelectedOperations.Selected' 'ListItems.SelectedOperations.Selected' 'Files.SelectedOperations.Selected' ) $SupportedSharePointRestPermissions = @( 'Sites.Selected' ) function Test-LocalBlank { [CmdletBinding()] param( [Parameter()] [AllowNull()] [object]$Value ) if ($null -eq $Value) { return $true } return ([string]$Value).Trim().Length -eq 0 } function ConvertTo-NormalizedStringArray { [CmdletBinding()] param( [Parameter()] [AllowNull()] [object[]]$Value ) return @( $Value | Where-Object { $null -ne $_ -and -not (Test-LocalBlank $_) } | ForEach-Object { ([string]$_).Trim() } | Sort-Object -Unique ) } function Get-MissingValues { [CmdletBinding()] param( [Parameter()] [AllowNull()] [object[]]$Granted, [Parameter()] [AllowNull()] [object[]]$Required ) $normalizedGranted = @( ConvertTo-NormalizedStringArray -Value $Granted ) $normalizedRequired = @( ConvertTo-NormalizedStringArray -Value $Required ) return @( $normalizedRequired | Where-Object { $_ -notin $normalizedGranted } ) } function Get-GraphCollection { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Uri ) $results = New-Object System.Collections.Generic.List[object] $nextUri = $Uri while (-not (Test-LocalBlank $nextUri)) { $response = Invoke-MgGraphRequest ` -Method GET ` -Uri $nextUri ` -ErrorAction Stop if ($null -ne $response.value) { foreach ($item in @($response.value)) { [void]$results.Add($item) } } elseif ($null -ne $response) { [void]$results.Add($response) } $nextUri = $null if ( $null -ne $response -and $response.PSObject.Properties.Name -contains '@odata.nextLink' ) { $nextUri = [string]$response.'@odata.nextLink' } } return @($results.ToArray()) } function Get-SingleServicePrincipalByAppId { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$AppId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Description, [Parameter()] [ValidateNotNullOrEmpty()] [string]$SelectProperties = 'id,appId,displayName' ) $escapedAppId = $AppId.Replace("'", "''") $uri = 'https://graph.microsoft.com/v1.0/servicePrincipals' + "?`$filter=appId eq '$escapedAppId'" + "&`$select=$SelectProperties" $matches = @( Get-GraphCollection -Uri $uri ) if ($matches.Count -eq 0) { throw ( "$Description service principal was not found " + "for AppId '$AppId'." ) } if ($matches.Count -gt 1) { throw ( "Multiple service principals were found for " + "$Description AppId '$AppId'." ) } return $matches[0] } function New-FailedValidationResult { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ErrorMessage ) return [pscustomobject]@{ Status = 'Skipped' Error = $ErrorMessage Worker = [pscustomobject]@{ ServicePrincipalId = '' AppId = $WorkerAppId DisplayName = '' } Graph = [pscustomobject]@{ ServicePrincipalId = '' ServicePrincipalAppId = $MicrosoftGraphAppId ServicePrincipalDisplayName = 'Microsoft Graph' Application = [pscustomobject]@{ GrantedRoles = @() RequiredRoles = @( ConvertTo-NormalizedStringArray ` -Value $RequiredGraphAppRoles ) MissingRoles = @( ConvertTo-NormalizedStringArray ` -Value $RequiredGraphAppRoles ) } Delegated = [pscustomobject]@{ GrantedScopes = @() RequiredScopes = @( ConvertTo-NormalizedStringArray ` -Value $RequiredGraphDelegatedScopes ) MissingScopes = @( ConvertTo-NormalizedStringArray ` -Value $RequiredGraphDelegatedScopes ) DetectionStatus = 'Skipped' Note = $ErrorMessage } SupportedSelectedPermissions = @( $SupportedGraphSelectedPermissions ) SelectedApplicationRolesGranted = @() HasSitesSelectedApplicationRole = $false RequirementsSpecified = $false RequirementsSatisfied = $false IsUsableForSelectedApplicationAccess = $false } SharePoint = [pscustomobject]@{ ServicePrincipalId = '' ServicePrincipalAppId = $SharePointOnlineAppId ServicePrincipalDisplayName = 'Office 365 SharePoint Online' Application = [pscustomobject]@{ GrantedRoles = @() RequiredRoles = @( ConvertTo-NormalizedStringArray ` -Value $RequiredSharePointAppRoles ) MissingRoles = @( ConvertTo-NormalizedStringArray ` -Value $RequiredSharePointAppRoles ) } Delegated = [pscustomobject]@{ GrantedScopes = @() RequiredScopes = @( ConvertTo-NormalizedStringArray ` -Value $RequiredSharePointDelegatedScopes ) MissingScopes = @( ConvertTo-NormalizedStringArray ` -Value $RequiredSharePointDelegatedScopes ) DetectionStatus = 'Skipped' Note = $ErrorMessage } SupportedRestPermissions = @( $SupportedSharePointRestPermissions ) RestApplicationRolesGranted = @() HasSitesSelectedApplicationRole = $false RequirementsSpecified = $false RequirementsSatisfied = $false IsUsableForSelectedApplicationAccess = $false } Summary = [pscustomobject]@{ WorkerAppId = $WorkerAppId EvaluationMode = 'Failed' GraphRequirementsSpecified = $false SharePointRequirementsSpecified = $false AnyRequirementsSpecified = $false EffectiveApplicationRoles = @() EffectiveDelegatedScopes = @() GraphApiUsable = $false SharePointRestApiUsable = $false AnyApplicationApiUsable = $false HasGraphSitesSelectedAppRole = $false HasSharePointSitesSelectedAppRole = $false BothApisMissingSitesSelected = $true RequestedPermissionsSatisfied = $false } } } try { $requiredGraphApp = @( ConvertTo-NormalizedStringArray ` -Value $RequiredGraphAppRoles ) $requiredGraphDelegated = @( ConvertTo-NormalizedStringArray ` -Value $RequiredGraphDelegatedScopes ) $requiredSharePointApp = @( ConvertTo-NormalizedStringArray ` -Value $RequiredSharePointAppRoles ) $requiredSharePointDelegated = @( ConvertTo-NormalizedStringArray ` -Value $RequiredSharePointDelegatedScopes ) $graphRequirementsSpecified = ( $requiredGraphApp.Count -gt 0 -or $requiredGraphDelegated.Count -gt 0 ) $sharePointRequirementsSpecified = ( $requiredSharePointApp.Count -gt 0 -or $requiredSharePointDelegated.Count -gt 0 ) $anyRequirementsSpecified = ( $graphRequirementsSpecified -or $sharePointRequirementsSpecified ) $evaluationMode = if ($anyRequirementsSpecified) { 'RequestedPermissions' } else { 'Discovery' } $graphSp = Get-SingleServicePrincipalByAppId ` -AppId $MicrosoftGraphAppId ` -Description 'Microsoft Graph' ` -SelectProperties 'id,appId,displayName,appRoles' $sharePointSp = Get-SingleServicePrincipalByAppId ` -AppId $SharePointOnlineAppId ` -Description 'Office 365 SharePoint Online' ` -SelectProperties 'id,appId,displayName,appRoles' $workerSp = Get-SingleServicePrincipalByAppId ` -AppId $WorkerAppId ` -Description 'Worker application' ` -SelectProperties 'id,appId,displayName' $graphRoleIdToValue = @{} foreach ($role in @($graphSp.appRoles)) { $roleId = [string]$role.id $roleValue = [string]$role.value if ( -not (Test-LocalBlank $roleId) -and -not (Test-LocalBlank $roleValue) ) { $graphRoleIdToValue[$roleId] = $roleValue } } $sharePointRoleIdToValue = @{} foreach ($role in @($sharePointSp.appRoles)) { $roleId = [string]$role.id $roleValue = [string]$role.value if ( -not (Test-LocalBlank $roleId) -and -not (Test-LocalBlank $roleValue) ) { $sharePointRoleIdToValue[$roleId] = $roleValue } } $assignmentUri = 'https://graph.microsoft.com/v1.0/servicePrincipals/' + $workerSp.id + '/appRoleAssignments' + '?$select=id,appRoleId,resourceId,resourceDisplayName' $assignments = @( Get-GraphCollection -Uri $assignmentUri ) $graphAppRoleList = New-Object System.Collections.Generic.List[string] $sharePointAppRoleList = New-Object System.Collections.Generic.List[string] foreach ($assignment in $assignments) { $resourceId = [string]$assignment.resourceId $appRoleId = [string]$assignment.appRoleId if ( $resourceId -eq [string]$graphSp.id -and $graphRoleIdToValue.ContainsKey($appRoleId) ) { [void]$graphAppRoleList.Add( $graphRoleIdToValue[$appRoleId] ) } if ( $resourceId -eq [string]$sharePointSp.id -and $sharePointRoleIdToValue.ContainsKey($appRoleId) ) { [void]$sharePointAppRoleList.Add( $sharePointRoleIdToValue[$appRoleId] ) } } $graphAppGranted = @( ConvertTo-NormalizedStringArray ` -Value $graphAppRoleList.ToArray() ) $sharePointAppGranted = @( ConvertTo-NormalizedStringArray ` -Value $sharePointAppRoleList.ToArray() ) $graphDelegatedGranted = @() $sharePointDelegatedGranted = @() $delegatedDetectionStatus = 'Pass' $delegatedDetectionNote = '' try { $delegatedUri = 'https://graph.microsoft.com/v1.0/servicePrincipals/' + $workerSp.id + '/oauth2PermissionGrants' + '?$select=id,clientId,consentType,principalId,resourceId,scope' $delegatedGrants = @( Get-GraphCollection -Uri $delegatedUri ) $graphDelegatedList = New-Object System.Collections.Generic.List[string] $sharePointDelegatedList = New-Object System.Collections.Generic.List[string] foreach ($grant in $delegatedGrants) { if (Test-LocalBlank $grant.scope) { continue } $scopes = @( ([string]$grant.scope) -split '\s+' | Where-Object { -not (Test-LocalBlank $_) } ) if ([string]$grant.resourceId -eq [string]$graphSp.id) { foreach ($scope in $scopes) { [void]$graphDelegatedList.Add($scope) } } elseif ( [string]$grant.resourceId -eq [string]$sharePointSp.id ) { foreach ($scope in $scopes) { [void]$sharePointDelegatedList.Add($scope) } } } $graphDelegatedGranted = @( ConvertTo-NormalizedStringArray ` -Value $graphDelegatedList.ToArray() ) $sharePointDelegatedGranted = @( ConvertTo-NormalizedStringArray ` -Value $sharePointDelegatedList.ToArray() ) } catch { $delegatedDetectionStatus = 'Skipped' $delegatedDetectionNote = ( 'Delegated permission detection was skipped. Error: ' + $_.Exception.Message ) } $graphAppMissing = @( Get-MissingValues ` -Granted $graphAppGranted ` -Required $requiredGraphApp ) $graphDelegatedMissing = @( Get-MissingValues ` -Granted $graphDelegatedGranted ` -Required $requiredGraphDelegated ) $sharePointAppMissing = @( Get-MissingValues ` -Granted $sharePointAppGranted ` -Required $requiredSharePointApp ) $sharePointDelegatedMissing = @( Get-MissingValues ` -Granted $sharePointDelegatedGranted ` -Required $requiredSharePointDelegated ) $graphSelectedAppGranted = @( $graphAppGranted | Where-Object { $_ -in $SupportedGraphSelectedPermissions } | Sort-Object -Unique ) $sharePointRestAppGranted = @( $sharePointAppGranted | Where-Object { $_ -in $SupportedSharePointRestPermissions } | Sort-Object -Unique ) $hasGraphSitesSelected = ( $graphAppGranted -contains 'Sites.Selected' ) $hasSharePointSitesSelected = ( $sharePointAppGranted -contains 'Sites.Selected' ) $graphApplicationRequirementsSatisfied = ( $graphAppMissing.Count -eq 0 ) $graphDelegatedRequirementsSatisfied = ( $graphDelegatedMissing.Count -eq 0 -and ( $requiredGraphDelegated.Count -eq 0 -or $delegatedDetectionStatus -eq 'Pass' ) ) $graphRequirementsSatisfied = ( $graphApplicationRequirementsSatisfied -and $graphDelegatedRequirementsSatisfied ) $sharePointApplicationRequirementsSatisfied = ( $sharePointAppMissing.Count -eq 0 ) $sharePointDelegatedRequirementsSatisfied = ( $sharePointDelegatedMissing.Count -eq 0 -and ( $requiredSharePointDelegated.Count -eq 0 -or $delegatedDetectionStatus -eq 'Pass' ) ) $sharePointRequirementsSatisfied = ( $sharePointApplicationRequirementsSatisfied -and $sharePointDelegatedRequirementsSatisfied ) if ($anyRequirementsSpecified) { $graphApiUsable = ( $graphRequirementsSpecified -and $graphRequirementsSatisfied ) $sharePointRestApiUsable = ( $sharePointRequirementsSpecified -and $sharePointRequirementsSatisfied ) } else { $graphApiUsable = ( $graphSelectedAppGranted.Count -gt 0 ) $sharePointRestApiUsable = ( $sharePointRestAppGranted.Count -gt 0 ) } $anyApiUsable = ( $graphApiUsable -or $sharePointRestApiUsable ) if ($anyRequirementsSpecified) { $requestedPermissionsSatisfied = $anyApiUsable } else { $requestedPermissionsSatisfied = $true } $bothApisMissingSitesSelected = ( -not $hasGraphSitesSelected -and -not $hasSharePointSitesSelected ) $status = if ($anyApiUsable) { 'Pass' } else { 'Fail' } return [pscustomobject]@{ Status = $status Error = '' Worker = [pscustomobject]@{ ServicePrincipalId = [string]$workerSp.id AppId = [string]$workerSp.appId DisplayName = [string]$workerSp.displayName } Graph = [pscustomobject]@{ ServicePrincipalId = [string]$graphSp.id ServicePrincipalAppId = [string]$graphSp.appId ServicePrincipalDisplayName = [string]$graphSp.displayName Application = [pscustomobject]@{ GrantedRoles = $graphAppGranted RequiredRoles = $requiredGraphApp MissingRoles = $graphAppMissing } Delegated = [pscustomobject]@{ GrantedScopes = $graphDelegatedGranted RequiredScopes = $requiredGraphDelegated MissingScopes = $graphDelegatedMissing DetectionStatus = $delegatedDetectionStatus Note = $delegatedDetectionNote } SupportedSelectedPermissions = @( $SupportedGraphSelectedPermissions ) SelectedApplicationRolesGranted = $graphSelectedAppGranted HasSitesSelectedApplicationRole = $hasGraphSitesSelected RequirementsSpecified = $graphRequirementsSpecified RequirementsSatisfied = $graphRequirementsSatisfied IsUsableForSelectedApplicationAccess = $graphApiUsable } SharePoint = [pscustomobject]@{ ServicePrincipalId = [string]$sharePointSp.id ServicePrincipalAppId = [string]$sharePointSp.appId ServicePrincipalDisplayName = ( [string]$sharePointSp.displayName ) Application = [pscustomobject]@{ GrantedRoles = $sharePointAppGranted RequiredRoles = $requiredSharePointApp MissingRoles = $sharePointAppMissing } Delegated = [pscustomobject]@{ GrantedScopes = $sharePointDelegatedGranted RequiredScopes = $requiredSharePointDelegated MissingScopes = $sharePointDelegatedMissing DetectionStatus = $delegatedDetectionStatus Note = $delegatedDetectionNote } SupportedRestPermissions = @( $SupportedSharePointRestPermissions ) RestApplicationRolesGranted = $sharePointRestAppGranted HasSitesSelectedApplicationRole = $hasSharePointSitesSelected RequirementsSpecified = $sharePointRequirementsSpecified RequirementsSatisfied = $sharePointRequirementsSatisfied IsUsableForSelectedApplicationAccess = ( $sharePointRestApiUsable ) } Summary = [pscustomobject]@{ WorkerAppId = $WorkerAppId EvaluationMode = $evaluationMode GraphRequirementsSpecified = $graphRequirementsSpecified SharePointRequirementsSpecified = ( $sharePointRequirementsSpecified ) AnyRequirementsSpecified = $anyRequirementsSpecified EffectiveApplicationRoles = @( @($graphAppGranted) + @($sharePointAppGranted) | Sort-Object -Unique ) EffectiveDelegatedScopes = @( @($graphDelegatedGranted) + @($sharePointDelegatedGranted) | Sort-Object -Unique ) GraphApiUsable = $graphApiUsable SharePointRestApiUsable = $sharePointRestApiUsable AnyApplicationApiUsable = $anyApiUsable HasGraphSitesSelectedAppRole = $hasGraphSitesSelected HasSharePointSitesSelectedAppRole = ( $hasSharePointSitesSelected ) BothApisMissingSitesSelected = ( $bothApisMissingSitesSelected ) RequestedPermissionsSatisfied = ( $requestedPermissionsSatisfied ) } } } catch { return New-FailedValidationResult ` -ErrorMessage $_.Exception.ToString() } } try { $listError = $null Import-Module Microsoft.Graph.Sites -RequiredVersion 2.33.0 -ErrorAction Stop | Out-Null Connect-MgGraph ` -ClientId $ClientId ` -TenantId $TenantId ` -CertificateThumbprint $Thumbprint ` -NoWelcome | Out-Null if (-not $SitePath.StartsWith('/')) { $SitePath = '/' + $SitePath } $siteUri = 'https://graph.microsoft.com/v1.0/sites/{0}:{1}:' -f $TenantHost, $SitePath $site = Invoke-MgGraphRequest -Method GET -Uri $siteUri $siteId = $site.id $driveId = Resolve-DriveIdFromContainerName ` -SiteId $siteId ` -ContainerName $ContainerName $targetRelativePath = Join-GraphDriveRelativePath ` -FolderPath $FolderPath ` -FilePath $FilePath $encodedTargetRelativePath = '' $targetType = 'Unknown' $targetItem = $null if ([string]::IsNullOrWhiteSpace($targetRelativePath)) { if (-not $AllowRoot) { throw 'FolderPath and FilePath are both empty (library root). Use -AllowRoot ONLY if you truly want to target root.' } $targetType = 'LibraryRoot' $targetItem = Invoke-MgGraphRequest ` -Method GET ` -Uri ('https://graph.microsoft.com/v1.0/drives/{0}/root' -f $driveId) } else { $encodedTargetRelativePath = Convert-ToGraphDrivePath -Path $targetRelativePath $targetItem = Invoke-MgGraphRequest ` -Method GET ` -Uri ('https://graph.microsoft.com/v1.0/drives/{0}/root:/{1}:' -f $driveId, $encodedTargetRelativePath) if ($targetItem.file) { $targetType = 'File' } elseif ($targetItem.folder) { $targetType = 'Folder' } else { $targetType = 'DriveItem' } } $itemId = $targetItem.id $itemWebUrl = $targetItem.webUrl if ([string]::IsNullOrWhiteSpace([string]$itemId)) { throw "Could not resolve target driveItem id. TargetRelativePath='$targetRelativePath'." } $permsUri = 'https://graph.microsoft.com/v1.0/drives/{0}/items/{1}/permissions' -f $driveId, $itemId $permResp = Invoke-MgGraphRequest -Method GET -Uri $permsUri $perms = @($permResp.value) $appId = $TargetAppId.ToString() $pmatches = _GetAppPermissionMatches -permissions $perms -appId $appId $best = $null if ($pmatches.Count -gt 0) { $best = $pmatches | Sort-Object { _RoleRank $_.roles } -Descending | Select-Object -First 1 } $Changed = $false $Deleted = $false $Granted = $false $ActualRole = '' $permissionIds = @() if ($best) { $Granted = $true $ActualRole = (@($best.roles) | Select-Object -First 1) $permissionIds = $pmatches | ForEach-Object { $_.id } } switch ($Mode) { 'read' { $requiredScopes = @('Files.SelectedOperations.Selected') $validation = Test-WorkerAppPermissions_ExtendedWithDelegated ` -WorkerAppId $TargetAppId ` -RequiredGraphAppRoles $requiredScopes $listError = $validation | ConvertTo-Json -Compress -Depth 10 } 'grant' { if ($CleanDuplicates -and $pmatches.Count -gt 1 -and $best) { $toRemove = $pmatches | Where-Object { $_.id -ne $best.id } foreach ($p in $toRemove) { Invoke-MgGraphRequest ` -Method DELETE ` -Uri ('{0}/{1}' -f $permsUri, $p.id) | Out-Null $Deleted = $true } $permResp = Invoke-MgGraphRequest -Method GET -Uri $permsUri $perms = @($permResp.value) $pmatches = _GetAppPermissionMatches -permissions $perms -appId $appId $best = $null if ($pmatches.Count -gt 0) { $best = $pmatches | Sort-Object { _RoleRank $_.roles } -Descending | Select-Object -First 1 } $Granted = ($null -ne $best) $ActualRole = if ($best) { (@($best.roles) | Select-Object -First 1) } else { '' } $permissionIds = if ($pmatches) { $pmatches | ForEach-Object { $_.id } } else { @() } } if ($best -and (_Satisfies -existingRoles $best.roles -requestedRole $Role)) { } else { if ($best -and $ForceUpgrade) { foreach ($p in $pmatches) { Invoke-MgGraphRequest ` -Method DELETE ` -Uri ('{0}/{1}' -f $permsUri, $p.id) | Out-Null $Deleted = $true } } $body = @{ roles = @($Role) grantedToV2 = @{ application = @{ id = $appId } } } | ConvertTo-Json -Depth 10 -Compress Invoke-MgGraphRequest ` -Method POST ` -Uri $permsUri ` -Body $body ` -ContentType 'application/json' | Out-Null $Changed = $true $permResp = Invoke-MgGraphRequest -Method GET -Uri $permsUri $perms = @($permResp.value) $pmatches = _GetAppPermissionMatches -permissions $perms -appId $appId $best = $null if ($pmatches.Count -gt 0) { $best = $pmatches | Sort-Object { _RoleRank $_.roles } -Descending | Select-Object -First 1 } $Granted = ($null -ne $best) $ActualRole = if ($best) { (@($best.roles) | Select-Object -First 1) } else { '' } $permissionIds = if ($pmatches) { $pmatches | ForEach-Object { $_.id } } else { @() } } } 'delete' { if ($pmatches.Count -gt 0) { foreach ($p in $pmatches) { Invoke-MgGraphRequest ` -Method DELETE ` -Uri ('{0}/{1}' -f $permsUri, $p.id) | Out-Null $Deleted = $true } $Changed = $Deleted $permResp = Invoke-MgGraphRequest -Method GET -Uri $permsUri $perms = @($permResp.value) $pmatches = _GetAppPermissionMatches -permissions $perms -appId $appId $best = $null if ($pmatches.Count -gt 0) { $best = $pmatches | Sort-Object { _RoleRank $_.roles } -Descending | Select-Object -First 1 } $Granted = ($null -ne $best) $ActualRole = if ($best) { (@($best.roles) | Select-Object -First 1) } else { '' } $permissionIds = if ($pmatches) { $pmatches | ForEach-Object { $_.id } } else { @() } } } } $result = @{ Success = $true Data = @{ Mode = $Mode TenantHost = $TenantHost SitePath = $SitePath ContainerName = $ContainerName FolderPath = $FolderPath FilePath = $FilePath SiteId = $siteId DriveId = $driveId ItemId = $itemId TargetType = $targetType TargetRelativePath = $targetRelativePath EncodedTargetRelativePath = $encodedTargetRelativePath ItemWebUrl = $itemWebUrl TargetAppId = $appId Granted = $Granted Role = $ActualRole PermissionIds = ($permissionIds -join ',') Changed = $Changed Deleted = $Deleted RequestedRole = $Role ListError = $listError } } } catch { $result = @{ Success = $false Error = $_.Exception.ToString() } } Write-Output ('__JSON__' + ($result | ConvertTo-Json -Compress -Depth 10)) |