WhiteboardAdmin.psm1
# # Constants # $Script:ServiceEndpoint = 'https://whiteboard.microsoft.com/api/v1.0' # Set Resource URI to Whiteboard $Script:ResourceAppIdUri = 'https://whiteboard.microsoft.com' # Azure AD tenant authority $Script:AuthUri = 'https://login.microsoftonline.com/common/' $Script:UserAgent = 'WhiteboardAdminModule/1.0' # # Cmdlets # <# .SYNOPSIS Gets one or more Whiteboards from the Microsoft Whiteboard service and returns them as objects. .PARAMETER Token The Azure AD bearer token corresponding to the specified credentials. If unspecified, a new token will be generated. .PARAMETER UserId (Optional) The ID of the user account to query Whiteboards for. All Whiteboards this account has access to will be returned. .PARAMETER WhiteboardId (Optional) The ID of a specific Whiteboard. .PARAMETER ForceAuthPrompt (Optional) Always prompt for auth. Use to ignore cached credentials. .EXAMPLE Get-Whiteboard -UserId 00000000-0000-0000-0000-000000000001 Get all of a user's Whiteboards. #> function Get-Whiteboard { [CmdletBinding()] param( [Parameter(Mandatory=$false)] [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult]$Token, [Parameter(Mandatory=$true, ParameterSetName='ForUser')] [Parameter(Mandatory=$true, ParameterSetName='SingleWhiteboard')] [Guid]$UserId, [Parameter(Mandatory=$true, ParameterSetName='SingleWhiteboard')] [Guid]$WhiteboardId, [Parameter(Mandatory=$false)] [switch]$ForceAuthPrompt) if ($WhiteboardId -eq $null) { $tokenAndResult = Get-WhiteboardInternal -Token $Token -UserId $UserId -ForceAuthPrompt:$ForceAuthPrompt } else { $tokenAndResult = Get-WhiteboardInternal -Token $Token -UserId $UserId -WhiteboardId $WhiteboardId -ForceAuthPrompt:$ForceAuthPrompt } return $tokenAndResult.RequestResult } <# .SYNOPSIS Sets the owner for a Whiteboard. .PARAMETER Token The Azure AD bearer token corresponding to the specified credentials. If unspecified, a new token will be generated. .PARAMETER WhiteboardId The Whiteboard for which the owner is being changed. .PARAMETER OldOwnerId The ID of the previous owner. .PARAMETER NewOwnerId The ID of the new owner. .PARAMETER ForceAuthPrompt (Optional) Always prompt for auth. Use to ignore cached credentials. .EXAMPLE Set-WhiteboardOwner -OldOwnerId 00000000-0000-0000-0000-000000000001 -NewOwnerId 00000000-0000-0000-0000-000000000002 Move a Whiteboard from one user to another. #> function Set-WhiteboardOwner { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact='High')] param( [Parameter(Mandatory=$false)] [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult]$Token, [Parameter(Mandatory=$true)] [Guid]$WhiteboardId, [Parameter(Mandatory=$true)] [Guid]$OldOwnerId, [Parameter(Mandatory=$true)] [Guid]$NewOwnerId, [Parameter(Mandatory=$false)] [switch]$ForceAuthPrompt) if ($PSCmdlet.ShouldProcess("Whiteboard: $WhiteboardId")) { $uri = "$ServiceEndpoint/users/$OldOwnerId/whiteboards/$WhiteboardId" # Manually convert the body to JSON (Invoke-RestMethod tries to URL encode the body if you pass it a hash) $body = ConvertTo-Json -Compress @(@{"op"="replace"; "path"="/OwnerId"; "value"=$NewOwnerId }) $tokenAndResult = Invoke-RestMethodWithAuth ` -Token $Token ` -ForceAuthPrompt:$ForceAuthPrompt ` -RestMethodParams @{'Method' = 'PATCH'; 'Uri' = $uri; 'ContentType' = 'application/json-patch+json'; 'UserAgent' = $UserAgent; 'Body' = $body } return $tokenAndResult.RequestResult } } <# .SYNOPSIS Transfer ownership of all Whiteboards owned by a user to another user. .PARAMETER Token The Azure AD bearer token corresponding to the specified credentials. If unspecified, a new token will be generated. .PARAMETER OldOwnerId The ID of the previous owner. .PARAMETER NewOwnerId The ID of the new owner. .PARAMETER WhatIf Execute the command without making any actual changes. Only calls read methods on the REST service. .PARAMETER ForceAuthPrompt (Optional) Always prompt for auth. Use to ignore cached credentials. .EXAMPLE Invoke-TransferAllWhiteboards -OldOwnerId 00000000-0000-0000-0000-000000000001 -NewOwnerId 00000000-0000-0000-0000-000000000002 -WhatIf Check how many Whiteboards will be transferred without transferring them. .EXAMPLE Invoke-TransferAllWhiteboards -OldOwnerId 00000000-0000-0000-0000-000000000001 -NewOwnerId 00000000-0000-0000-0000-000000000002 Transfer (and prompt before performing any write actions). #> function Invoke-TransferAllWhiteboards { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact='High')] param( [Parameter(Mandatory=$false)] [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult]$Token, [Parameter(Mandatory=$true)] [Guid]$OldOwnerId, [Parameter(Mandatory=$true)] [Guid]$NewOwnerId, [Parameter(Mandatory=$false)] [switch]$ForceAuthPrompt) $whiteboardsAndToken = Get-WhiteboardInternal -Token $Token -UserId $OldOwnerId -ForceAuthPrompt:$ForceAuthPrompt $Token = $whiteboardsAndToken.Token $whiteboards = $whiteboardsAndToken.RequestResult # Only transfer Whiteboards actually owned by this Id $whiteboardsOwned = @($whiteboards | Where-Object { [Guid]::Parse($_.ownerId) -eq $OldOwnerId }) Write-Verbose "Found $($whiteboardsOwned.Length) Whiteboards for owner $($OldOwnerId)" if ($PSCmdlet.ShouldProcess("Whiteboards for Owner: $OldOwnerId")) { $whiteboardsOwned | ForEach-Object { Set-WhiteboardOwner -Token $Token -OldOwnerId $OldOwnerId -NewOwnerId $NewOwnerId -WhiteboardId $_.id -Confirm:$false } } } # # Private methods # function Get-WhiteboardInternal { [CmdletBinding()] param( [Parameter(Mandatory=$false)] [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult]$Token, [Parameter(Mandatory=$true)] [Guid]$UserId, [Parameter(Mandatory=$false)] [Guid]$WhiteboardId, [Parameter(Mandatory=$false)] [switch]$ForceAuthPrompt) if ($null -ne $UserId) { $uri = "$ServiceEndpoint/users/$UserId/whiteboards" } else { $uri = "$ServiceEndpoint/whiteboards" } $tokenAndResult = Invoke-RestMethodWithAuth ` -Token $Token ` -ForceAuthPrompt:$ForceAuthPrompt ` -RestMethodParams @{'Method' = 'GET'; 'Uri' = $uri; 'ContentType' = 'application/json'; 'UserAgent' = $UserAgent } if ($null -ne $WhiteboardId) { $filteredResult = $tokenAndResult.RequestResult | ? { [Guid]::Parse($_.id) -eq $WhiteboardId } } else { $filteredResult = $tokenAndResult.RequestResult } return New-Object -TypeName PSObject -Prop @{'RequestResult' = $filteredResult; 'Token' = $Token} } function Invoke-RestMethodWithAuth( [Parameter(Mandatory=$false)] [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult]$Token, [Parameter(Mandatory=$false)] [switch]$ForceAuthPrompt, [Parameter(Mandatory=$true)] [Hashtable]$RestMethodParams) { # Use the clientId from the Azure PowerShell module $clientId = '1950a258-227b-4e31-a9cf-717495945fc2' $redirectUri = 'urn:ietf:wg:oauth:2.0:oob' Write-Verbose "Authority URI: $AuthUri" $authContext = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($AuthUri) if ($ForceAuthPrompt -eq $true) { $platformParameters = [Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters]::new( [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always) } else { $platformParameters = [Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters]::new( [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto) } $userIdentifier = [Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier]::AnyUser # No token specified, so get a new one if ($Token -eq $null) { $Token = $authContext.AcquireTokenAsync($Script:ResourceAppIdUri, $clientId, $redirectUri, $platformParameters, $userIdentifier).Result } try { Set-RequestHeader -Token $Token -RestMethodParams $RestMethodParams $requestResult = Invoke-RestMethod @RestMethodParams Write-Verbose "No need to rebuild token" } catch [System.Net.WebException] { Write-Verbose "Rebuilding token from claims" $ex = $_ $stream = $ex.Exception.Response.GetResponseStream() $streamReader = [System.IO.StreamReader]::new($stream) $content = $streamReader.ReadToEnd() # If unable to parse the claims JSON, throw the outer exception try { $claims = ($content | ConvertFrom-Json).claims } catch { throw $ex } # The second token acquisition should trigger MFA in the cases where it is enabled $Token = $authContext.AcquireTokenAsync($Script:ResourceAppIdUri, $clientId, $redirectUri, $platformParameters, $userIdentifier, "claims=$claims").Result Set-RequestHeader -Token $Token -RestMethodParams $RestMethodParams # Invoke the method again now that we have the full auth token $requestResult = Invoke-RestMethod @RestMethodParams } return New-Object -TypeName PSObject -Prop @{'RequestResult' = $requestResult; 'Token' = $Token} } # Replace the auth header only function Set-RequestHeader( [Parameter(Mandatory=$true)] [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult]$Token, [Parameter(Mandatory=$true)] [Hashtable]$RestMethodParams) { if (-not $RestMethodParams.ContainsKey('Headers')) { $RestMethodParams['Headers'] = @{} } $RestMethodParams['Headers']['Authorization'] = $Token.CreateAuthorizationHeader() } |