public/Set-odscexShortcutState.ps1
|
function Set-odscexShortcutState { [CmdletBinding(DefaultParameterSetName = 'UserPrincipalName', SupportsShouldProcess)] param( [Parameter(Mandatory = $true, ParameterSetName = 'UserPrincipalName')] [Parameter(Mandatory = $true, ParameterSetName = 'UserObjectId')] [string] $Uri, [Parameter(Mandatory = $false, ParameterSetName = 'UserPrincipalName')] [Parameter(Mandatory = $false, ParameterSetName = 'UserObjectId')] [string] $DocumentLibrary, [Parameter(Mandatory = $false, ParameterSetName = 'UserPrincipalName')] [Parameter(Mandatory = $false, ParameterSetName = 'UserObjectId')] [string] $DocumentLibraryId, [Parameter(Mandatory = $false, ParameterSetName = 'UserPrincipalName')] [Parameter(Mandatory = $false, ParameterSetName = 'UserObjectId')] [string] $FolderPath, [Parameter(Mandatory = $false, ParameterSetName = 'UserPrincipalName')] [Parameter(Mandatory = $false, ParameterSetName = 'UserObjectId')] [string] $RelativePath, [Parameter(Mandatory = $false, ParameterSetName = 'UserPrincipalName')] [Parameter(Mandatory = $false, ParameterSetName = 'UserObjectId')] [string] $ShortcutName, [Parameter(Mandatory = $true, ParameterSetName = 'UserPrincipalName', ValueFromPipelineByPropertyName = $true)] [Alias('Mail')] [string] $UserPrincipalName, [Parameter(Mandatory = $true, ParameterSetName = 'UserObjectId', ValueFromPipelineByPropertyName = $true)] [Alias('Id', 'UserId')] [string] $UserObjectId, [Parameter(Mandatory = $false)] [ValidateSet('Present', 'Absent')] [string] $State = 'Present', [Parameter(Mandatory = $false)] [ValidateSet('Skip', 'Replace', 'Rename', 'Error')] [string] $ConflictAction = 'Skip', [Parameter(Mandatory = $false)] [switch] $AllowAmbiguousLibraryMatch, [Parameter(Mandatory = $false)] [switch] $PassThru ) process { if (-not $DocumentLibrary -and -not $DocumentLibraryId) { Write-Error 'Specify -DocumentLibrary or -DocumentLibraryId.' -ErrorAction Stop } $User = switch ($PsCmdlet.ParameterSetName) { 'UserPrincipalName' { $UserPrincipalName } 'UserObjectId' { $UserObjectId } } $Target = Resolve-odscexShortcutTarget -Uri $Uri -DocumentLibrary $DocumentLibrary -DocumentLibraryId $DocumentLibraryId -FolderPath $FolderPath -AllowAmbiguousLibraryMatch:$AllowAmbiguousLibraryMatch if (-not $ShortcutName) { $ShortcutName = $Target.DefaultShortcutName } $ShortcutResource = Join-odscexDrivePathResource -User $User -RelativePath $RelativePath -Name $ShortcutName $ExistingShortcut = $null try { $ExistingShortcut = Invoke-odscexApiRequest -Resource $ShortcutResource -Method ([Microsoft.PowerShell.Commands.WebRequestMethod]::Get) -ErrorAction Stop } catch { $StatusCode = Get-odscexGraphStatusCode -ErrorRecord $_ if ($StatusCode -eq 404) { $ExistingShortcut = $null } elseif ($StatusCode -eq 403) { Write-Error "Unable to check for existing shortcut '$ShortcutName' for '$User'. Microsoft Graph returned 403 for $ShortcutResource. Verify permission to read the user's OneDrive." -ErrorAction Stop } else { Write-Error "Unable to check for existing shortcut '$ShortcutName' for '$User'. $($_.Exception.Message)" -ErrorAction Stop } } if ($State -eq 'Absent') { if (-not $ExistingShortcut) { return Write-odscexResult -User $User -ShortcutName $ShortcutName -Action 'Remove' -Status 'AlreadyAbsent' -Message 'Shortcut was already absent.' -TargetSite $Uri -TargetLibrary $DocumentLibrary -TargetFolderPath $FolderPath } if (-not $ExistingShortcut.remoteItem) { Write-Error "Existing item '$ShortcutName' for '$User' is not a OneDrive shortcut." -ErrorAction Stop } if ($PSCmdlet.ShouldProcess("${User}'s OneDrive", "Removing shortcut '$ShortcutName'")) { Invoke-odscexApiRequest -Resource $ShortcutResource -Method ([Microsoft.PowerShell.Commands.WebRequestMethod]::Delete) | Out-Null return Write-odscexResult -User $User -ShortcutName $ShortcutName -Action 'Remove' -Status 'Removed' -Response $ExistingShortcut -TargetSite $Uri -TargetLibrary $DocumentLibrary -TargetFolderPath $FolderPath } return } if ($ExistingShortcut) { if (Test-odscexShortcutTargetMatch -Shortcut $ExistingShortcut -Target $Target) { return Write-odscexResult -User $User -ShortcutName $ShortcutName -Action 'None' -Status 'Compliant' -Response $ExistingShortcut -Message 'Shortcut already points to the requested target.' -TargetSite $Uri -TargetLibrary $DocumentLibrary -TargetFolderPath $FolderPath } switch ($ConflictAction) { 'Skip' { return Write-odscexResult -User $User -ShortcutName $ShortcutName -Action 'None' -Status 'SkippedConflict' -Response $ExistingShortcut -Message 'An item with the requested shortcut name already exists.' -TargetSite $Uri -TargetLibrary $DocumentLibrary -TargetFolderPath $FolderPath } 'Error' { Write-Error "An item named '$ShortcutName' already exists for '$User' and does not match the requested target." -ErrorAction Stop } 'Replace' { if ($PSCmdlet.ShouldProcess("${User}'s OneDrive", "Replacing shortcut '$ShortcutName'")) { Invoke-odscexApiRequest -Resource $ShortcutResource -Method ([Microsoft.PowerShell.Commands.WebRequestMethod]::Delete) | Out-Null $ExistingShortcut = $null } else { return } } 'Rename' { $ShortcutName = "$ShortcutName-$([DateTime]::UtcNow.ToString('yyyyMMddHHmmss'))" $ShortcutResource = Join-odscexDrivePathResource -User $User -RelativePath $RelativePath -Name $ShortcutName } } } $OneDriveRoot = Resolve-odscexOneDriveRoot -User $User $RemoteItem = New-odscexRemoteItemReference -Target $Target -ShortcutName $ShortcutName $OneDriveDriveId = $OneDriveRoot.parentReference.driveId # Prefer drive-scoped item URLs after the destination drive is known. Microsoft Graph can # reject remoteItem create/move requests that are routed through /users/{id}/drive. $RootCreateResource = if ($OneDriveDriveId) { Join-odscexDriveItemResource -DriveId $OneDriveDriveId -ItemId $OneDriveRoot.id -Children } else { Join-odscexDriveItemResource -User $User -ItemId $OneDriveRoot.id -Children } $CreateRequest = @{ Resource = $RootCreateResource Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Post Body = @{ name = $ShortcutName remoteItem = $RemoteItem '@microsoft.graph.conflictBehavior' = if ($ConflictAction -eq 'Rename') { 'rename' } else { 'fail' } } } if ($PSCmdlet.ShouldProcess("${User}'s OneDrive", "Creating shortcut '$ShortcutName'")) { $DestinationFolder = $null $DestinationDriveId = $null $MoveShortcutToDestination = $false if ($RelativePath) { $FolderResponse = Resolve-odscexDriveFolderPath -User $User -RelativePath $RelativePath -Create if (-not $FolderResponse) { Write-Error "Error resolving OneDrive folder path '$RelativePath' for ${User}." -ErrorAction Stop } $DestinationFolder = $FolderResponse $DestinationDriveId = $DestinationFolder.parentReference.driveId if (-not $DestinationDriveId) { $DestinationDriveId = $OneDriveDriveId } $CreateRequest.Resource = if ($DestinationDriveId) { Join-odscexDriveItemResource -DriveId $DestinationDriveId -ItemId $FolderResponse.id -Children } else { Join-odscexDriveItemResource -User $User -ItemId $FolderResponse.id -Children } } try { $ShortcutResponse = Invoke-odscexApiRequest @CreateRequest -ErrorAction Stop } catch { $StatusCode = Get-odscexGraphStatusCode -ErrorRecord $_ if (($StatusCode -ne 400) -or (-not $DestinationFolder)) { Write-Error $_ -ErrorAction Stop } Write-Verbose "Microsoft Graph rejected shortcut creation directly inside '$RelativePath'. Creating the shortcut at the OneDrive root, then moving it to the requested folder." $TemporaryShortcutName = "_odscex-$([Guid]::NewGuid().ToString('N'))" $CreateRequest.Resource = $RootCreateResource $CreateRequest.Body = @{ name = $TemporaryShortcutName remoteItem = $RemoteItem '@microsoft.graph.conflictBehavior' = 'fail' } $ShortcutResponse = Invoke-odscexApiRequest @CreateRequest $MoveShortcutToDestination = $true } if (!($ShortcutResponse)) { Write-Error "Error creating OneDrive shortcut '$($ShortcutName)' for ${User}." -ErrorAction Stop } try { if ($MoveShortcutToDestination) { $ShortcutResourceById = if ($DestinationDriveId) { Join-odscexDriveItemResource -DriveId $DestinationDriveId -ItemId $ShortcutResponse.id } else { Join-odscexDriveItemResource -User $User -ItemId $ShortcutResponse.id } $ShortcutResponse = Move-odscexDriveItemWithRetry -Resource $ShortcutResourceById -DestinationFolderId $DestinationFolder.id -RelativePath $RelativePath -ItemId $ShortcutResponse.id } $ShortcutResourceById = if ($DestinationDriveId) { Join-odscexDriveItemResource -DriveId $DestinationDriveId -ItemId $ShortcutResponse.id } elseif ($OneDriveDriveId) { Join-odscexDriveItemResource -DriveId $OneDriveDriveId -ItemId $ShortcutResponse.id } else { Join-odscexDriveItemResource -User $User -ItemId $ShortcutResponse.id } $RenameRequest = @{ Resource = $ShortcutResourceById Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Patch Body = @{ name = $ShortcutName } } $ShortcutResponse = Invoke-odscexApiRequest @RenameRequest -ErrorAction Stop } catch { if ($MoveShortcutToDestination) { try { Invoke-odscexApiRequest -Resource $ShortcutResourceById -Method ([Microsoft.PowerShell.Commands.WebRequestMethod]::Delete) -ErrorAction Stop | Out-Null } catch { Write-Verbose "Unable to clean up temporary shortcut '$($ShortcutResponse.id)' after fallback move or rename failure. $($_.Exception.Message)" } } Write-Error $_ -ErrorAction Stop } if ($PassThru) { return $ShortcutResponse } return Write-odscexResult -User $User -ShortcutName $ShortcutName -Action 'Create' -Status 'Created' -Response $ShortcutResponse -TargetSite $Uri -TargetLibrary $DocumentLibrary -TargetFolderPath $FolderPath } } } |