MSGraph.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\MSGraph.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = Get-PSFConfigValue -FullName MSGraph.Import.DoDotSource -Fallback $false if ($MSGraph_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = Get-PSFConfigValue -FullName MSGraph.Import.IndividualFiles -Fallback $false if ($PoShPRTG_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) { . Import-ModuleFile -Path $path } # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) { . Import-ModuleFile -Path $path } # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code <# This file loads the strings documents from the respective language folders. This allows localizing messages and errors. Load psd1 language files for each language you wish to support. Partial translations are acceptable - when missing a current language message, it will fallback to English or another available language. #> Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'MSGraph' -Language 'en-US' Add-Type -AssemblyName System.Net.Http Add-Type -AssemblyName System.Web Add-Type -AssemblyName System.Windows.Forms function Merge-HashToJson { <# .SYNOPSIS Merge a hashtable(s) object to a JSON data string(s) .DESCRIPTION Merge a hashtable(s) object to a JSON data string(s) Accepts [hashtable] object(s) as well as [System.Collections.Specialized.OrderedDictionary] object(s) Helper function used for internal commands. .PARAMETER Hashtable The hashtable to convert to json .PARAMETER OrderedHashtable A hash created by [ordered]@{} to convert to json .EXAMPLE PS C:\> Merge-HashToJson $hash Creates a json string from content in variable $hash. This is the recommend usage Variable $hash can be: $hash = @{ content = "this is a regular hashtable" } or $hash = [ordered]@{ content = "this is a ordered hashtable" } .EXAMPLE PS C:\> Merge-HashToJson -Hashtable $hash Creates a json string from content in variable $hash. Variable $hash has to be a regular hashtable: $hash = @{ content = "this is a regular hashtable" } .EXAMPLE PS C:\> Merge-HashToJson -OrderedHashtable $hash Creates a json string from content in variable $hash. Variable $hash has to be a ordered hashtable: $hash = @{ content = "this is a regular hashtable" } #> #[CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = "OrderedHash")] [CmdletBinding(ConfirmImpact = 'Low')] [OutputType([String])] param ( [Parameter(ParameterSetName = "HashTable", Position = 0, Mandatory = $true)] [hashtable[]] $Hashtable, [Parameter(ParameterSetName = "OrderedHash", Position = 0, Mandatory = $true)] [System.Collections.Specialized.OrderedDictionary[]] $OrderedHashtable ) begin { } process { if ($PSCmdlet.ParameterSetName -like "OrderedHash") { $Hashtable = [ordered]@{} $Hashtable = $OrderedHashtable } Write-PSFMessage -Level Debug -Message "Merge hashtable with key(s) ('$([string]::Join("', '", $Hashtable.Keys))') by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($hash in $Hashtable) { $JsonParts = @() foreach ($key in $hash.Keys) { $JsonParts = $JsonParts + """$($key)"" : $($hash[$key])" } $json = "{`n" + ([string]::Join(",`n", $JsonParts)) + "`n}" $json } } end { } } function Convert-UriQueryFromHash { <# .SYNOPSIS Converts hashtables to a string for REST api calls. .DESCRIPTION Converts hashtables to a string for REST api calls. .PARAMETER hash The hashtable to convert to a string .PARAMETER NoQuestionmark Supress the ? as the first character in the output string .EXAMPLE PS C:\> Convert-UriQueryFromHash -Hash @{ username = "user"; password = "password"} Converts the specified hashtable to the following string: ?password=password&username=user .EXAMPLE PS C:\> Convert-UriQueryFromHash -Hash @{ username = "user"; password = "password"} -NoQuestionmark Converts the specified hashtable to the following string: password=password&username=user #> [OutputType([System.String])] [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true)] [System.Collections.Hashtable] $Hash, [switch] $NoQuestionmark ) begin { } process { $elements = foreach ($key in $Hash.Keys) { $key + "=" + $Hash[$key] } $elementString = [string]::Join("&", $elements) if ($NoQuestionMark) { "$elementString" } else { "?$elementString" } } end { } } function ConvertFrom-Base64StringWithNoPadding( [string]$Data ) { <# .SYNOPSIS Helper function build valid Base64 strings from JWT access tokens .DESCRIPTION Helper function build valid Base64 strings from JWT access tokens .PARAMETER Data The Token to convert .EXAMPLE PS C:\> ConvertFrom-Base64StringWithNoPadding -Data $data build valid base64 string the content from variable $data #> $Data = $Data.Replace('-', '+').Replace('_', '/') switch ($Data.Length % 4) { 0 { break } 2 { $Data += '==' } 3 { $Data += '=' } default { throw New-Object ArgumentException('data') } } [System.Convert]::FromBase64String($Data) } function ConvertFrom-JWTtoken { <# .SYNOPSIS Converts access tokens to readable objects .DESCRIPTION Converts access tokens to readable objects .PARAMETER Token The Token to convert .EXAMPLE PS C:\> ConvertFrom-JWTtoken -Token $Token Converts the content from variable $token to an object #> [cmdletbinding()] param( [Parameter(Mandatory = $true)] [string] $Token ) # Validate as per https://tools.ietf.org/html/rfc7519 - Access and ID tokens are fine, Refresh tokens will not work if ((-not $Token.Contains(".")) -or (-not $Token.StartsWith("eyJ"))) { $msg = "Invalid data or not an access token. $($Token)" Stop-PSFFunction -Message $msg -Tag "JWT" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg)) } # Split the token in its parts $tokenParts = $Token.Split(".") # Work on header $tokenHeader = [System.Text.Encoding]::UTF8.GetString( (ConvertFrom-Base64StringWithNoPadding $tokenParts[0]) ) $tokenHeaderJSON = $tokenHeader | ConvertFrom-Json # Work on payload $tokenPayload = [System.Text.Encoding]::UTF8.GetString( (ConvertFrom-Base64StringWithNoPadding $tokenParts[1]) ) $tokenPayloadJSON = $tokenPayload | ConvertFrom-Json # Work on signature $tokenSignature = ConvertFrom-Base64StringWithNoPadding $tokenParts[2] # Output $resultObject = New-Object MSGraph.Core.JWTAccessTokenInfo $resultObject.Header = $tokenHeader $resultObject.Payload = $tokenPayload $resultObject.Signature = $tokenSignature $resultObject.Algorithm = $tokenHeaderJSON.alg $resultObject.Type = $tokenHeaderJSON.typ if ($tokenPayloadJSON.appid) { $resultObject.ApplicationID = $tokenPayloadJSON.appid } $resultObject.ApplicationName = $tokenPayloadJSON.app_displayname $resultObject.Issuer = $tokenPayloadJSON.iss $resultObject.Audience = $tokenPayloadJSON.aud $resultObject.AuthenticationMethod = $tokenPayloadJSON.amr $resultObject.ExpirationTime = ([datetime]"1970-01-01Z00:00:00").AddSeconds($tokenPayloadJSON.exp).ToUniversalTime() $resultObject.GivenName = $tokenPayloadJSON.given_name $resultObject.IssuedAt = ([datetime]"1970-01-01Z00:00:00").AddSeconds($tokenPayloadJSON.iat).ToUniversalTime() $resultObject.Name = $tokenPayloadJSON.name $resultObject.NotBefore = ([datetime]"1970-01-01Z00:00:00").AddSeconds($tokenPayloadJSON.nbf).ToUniversalTime() if ($tokenPayloadJSON.oid) { $resultObject.OID = $tokenPayloadJSON.oid } $resultObject.Plattform = $tokenPayloadJSON.platf $resultObject.Scope = $tokenPayloadJSON.scp $resultObject.SID = $tokenPayloadJSON.onprem_sid $resultObject.SourceIPAddr = $tokenPayloadJSON.ipaddr $resultObject.SureName = $tokenPayloadJSON.family_name $resultObject.TenantID = $tokenPayloadJSON.tid $resultObject.UniqueName = $tokenPayloadJSON.unique_name $resultObject.UPN = $tokenPayloadJSON.upn $resultObject.Version = $tokenPayloadJSON.ver #$output $resultObject } function Invoke-TokenLifetimeValidation { <# .SYNOPSIS Validates the lifetime of a token object .DESCRIPTION Validates the lifetime of a token object and invoke update-token process, if needed. Helper function used for internal commands. .PARAMETER Token The Token to test and receive .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> Resolve-Token -User $Token Test Token for lifetime, or receives registered token from script variable #> [OutputType([MSGraph.Core.AzureAccessToken])] [CmdletBinding()] param ( [MSGraph.Core.AzureAccessToken] $Token, [String] $FunctionName = $MyInvocation.MyCommand ) process { $Token = Resolve-Token -Token $Token -FunctionName $FunctionName if ( (-not $Token.IsValid) -or ($Token.PercentRemaining -lt 15) ) { # if token is invalid or less then 15 percent of lifetime -> go and refresh the token Write-PSFMessage -Level Verbose -Message "Token lifetime is less then 15%. Initiate token refresh. Time remaining $($Token.TimeRemaining)" -Tag "Authentication" -FunctionName $FunctionName $paramsTokenRefresh = @{ Token = $Token PassThru = $true } if ($script:msgraph_Token.AccessTokenInfo.Payload -eq $Token.AccessTokenInfo.Payload) { $paramsTokenRefresh.Add("Register", $true) } if ($Token.Credential) { $paramsTokenRefresh.Add("Credential", $Token.Credential) } $Token = Update-MgaAccessToken @paramsTokenRefresh } else { Write-PSFMessage -Level Verbose -Message "Valid token for user $($Token.UserprincipalName) - Time remaining $($Token.TimeRemaining)" -Tag "Authentication" -FunctionName $FunctionName } $Token } } function Invoke-TokenScopeValidation { <# .SYNOPSIS Validates the scope of a token object .DESCRIPTION Validates the scope of a token object and invoke update-token process, if needed. Helper function used for internal commands. .PARAMETER Token The Token to test. .PARAMETER Scope The scope(s) the check for existence. .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .EXAMPLE PS C:\> $Token = Invoke-TokenScopeValidation -User $Token -Scope "Mail.Read" Test Token for scope and return the token. If necessary, the token will be renewed #> [OutputType([MSGraph.Core.AzureAccessToken])] [CmdletBinding()] param ( [MSGraph.Core.AzureAccessToken] $Token, [Parameter(Mandatory = $true)] [string[]] $Scope, [String] $FunctionName = $MyInvocation.MyCommand ) process { $Token = Resolve-Token -Token $Token -FunctionName $FunctionName if (-not (Test-TokenScope -Token $Token -Scope $requiredPermission -FunctionName $FunctionName)) { # required scope information are missing in token Write-PSFMessage -Level Warning -Message "Required scope information ($([String]::Join(", ",$Scope))) are missing in token." -Tag "Authentication" -FunctionName $FunctionName if ($Token.IdentityPlatformVersion -like '2.0') { # With Microsoft Identity Platform 2.0 it is possible to dynamically query new scope informations (incremental consent) Write-PSFMessage -Level Verbose -Message "Microsoft Identity Platform 2.0 is used. Dynamical permission request possible. Try to aquire new token." -Tag "Authentication" -FunctionName $FunctionName $Scope = $Scope + $Token.Scope $tenant = if ($Token.TenantID -like "9188040d-6c67-4c5b-b112-36a304b66dad") {'consumers'} else {'common'} # build parameters to query new token $paramsNewToken = @{ PassThru = $true ClientId = $Token.ClientId.ToString() RedirectUrl = $Token.AppRedirectUrl.ToString() ResourceUri = $Token.Resource.ToString().TrimEnd('/') IdentityPlatformVersion = $Token.IdentityPlatformVersion Permission = ($Scope | Where-Object { $_ -notin "offline_access", "openid", "profile", "email" }) Tenant = $tenant } if ($script:msgraph_Token.AccessTokenInfo.Payload -eq $Token.AccessTokenInfo.Payload) { $paramsNewToken.Add("Register", $true) } if ($Token.Credential) { $paramsNewToken.Add("Credential", $Token.Credential) } $Token = New-MgaAccessToken @paramsNewToken } else { Stop-PSFFunction -Message "FAILED, missing required scope information ($([String]::Join(", ",$Scope))) and Microsoft Identity Platform 1.0 is used.`nNo dynamic permission request available. Permissions has to be specified/granted in app registration process or portal." -EnableException $true -Category AuthenticationError -FunctionName $FunctionName } } else { Write-PSFMessage -Level VeryVerbose -Message "OK, required scope information are present. ($([String]::Join(", ",$Scope)))" -Tag "Authentication" -FunctionName $FunctionName } $Token } } function New-HttpClient { <# .SYNOPSIS Generates a HTTP Client. .DESCRIPTION Generates a HTTP Client for use with web services (REST Api). .PARAMETER UserAgentName The name of the UserAgent. .PARAMETER UserAgentVersion The Version of the UserAgent. .PARAMETER HeaderType Data language in the header. .EXAMPLE PS C:\> New-HttpClient Creates a Http Client with default values .EXAMPLE PS C:\> New-HttpClient -UserAgentName "PowerShellRestClient" -userAgentVersion "1.1" Creates a Http Client with UserAgent "PowerShellRestClient" as name and Version 1.1. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Alias('UserAgent')] [String] $UserAgentName = (Get-PSFConfigValue -FullName MSGraph.WebClient.UserAgentName -Fallback "PowerShellRestClient"), [Alias('Version')] [String] $userAgentVersion = (Get-PSFConfigValue -FullName MSGraph.WebClient.UserAgentVersion -Fallback "1.1"), [String] $HeaderType = "application/json" ) process { $header = New-Object System.Net.Http.Headers.MediaTypeWithQualityHeaderValue($HeaderType) $userAgent = New-Object System.Net.Http.Headers.ProductInfoHeaderValue($UserAgentName, $userAgentVersion) $handler = New-Object System.Net.Http.HttpClientHandler $handler.CookieContainer = New-Object System.Net.CookieContainer $handler.AllowAutoRedirect = $true $httpClient = New-Object System.Net.Http.HttpClient($handler) $httpClient.Timeout = New-Object System.TimeSpan(0, 0, 90) $httpClient.DefaultRequestHeaders.TransferEncodingChunked = $false $httpClient.DefaultRequestHeaders.Accept.Add($header) $httpClient.DefaultRequestHeaders.UserAgent.Add($userAgent) return $httpClient } } function Resolve-Token { <# .SYNOPSIS Test for specified Token, or receives registered token .DESCRIPTION Test for specified Token, or receives registered token. Helper function used for internal commands. .PARAMETER Token The Token to test and receive .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .EXAMPLE PS C:\> Resolve-Token -User $Token Test Token for lifetime, or receives registered token from script variable #> [OutputType([MSGraph.Core.AzureAccessToken])] [CmdletBinding()] param ( [MSGraph.Core.AzureAccessToken] $Token, [String] $FunctionName = $MyInvocation.MyCommand ) process { if (-not $Token) { Write-PSFMessage -Level Debug -Message "No token on parameter in command. Getting registered token." -Tag "Authentication" -FunctionName $FunctionName $Token = $script:msgraph_Token } if ($Token) { $Token } else { Stop-PSFFunction -Message "Not connected! Use New-MgaAccessToken to create a Token and either register it or specifs it" -EnableException $true -Category AuthenticationError -FunctionName $FunctionName } } } function Resolve-UserString { <# .SYNOPSIS Converts usernames or email addresses into the user targeting segment of the Rest Api call url. .DESCRIPTION Converts usernames or email addresses into the user targeting segment of the Rest Api call url. .PARAMETER User The user to convert .PARAMETER ContextData Specifies, that the user string should be resolved to a @odata.context field Different output is needed on context URLs. .EXAMPLE PS C:\> Resolve-UserString -User $User Resolves $User into a legitimate user targeting string element. .EXAMPLE PS C:\> Resolve-UserString -User $User -ContextData Resolves $User into a legitimate user string for a @odata.context element. #> [OutputType([System.String])] [CmdletBinding()] param ( [string] $User, [switch] $ContextData ) if ($User -eq 'me' -or (-not $User)) { return 'me' } if($ContextData) { if ($User -like "users('*") { return $User } else { $userEscaped = [uri]::EscapeDataString($User) return "users('$($userEscaped)')" } } else { if ($User -like "users/*") { return $User } else { return "users/$($User)" } } } function Show-OAuthWindow { <# .SYNOPSIS Generates a OAuth window for interactive authentication. .DESCRIPTION Generates a OAuth window for interactive authentication. .PARAMETER Url The url to the service offering authentication. .EXAMPLE PS C:\> Show-OAuthWindow -Url $uri Opens an authentication window to authenticate against the service pointed at in $uri #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Uri] $Url ) begin {} process {} end { # check screen resultion and calculate size for login form $screenResolution = Get-CimInstance -ClassName Win32_VideoController $formWidth = [math]::round(($screenResolution.CurrentHorizontalResolution / 4.36), 0) $formHeight = [math]::round(($screenResolution.CurrentVerticalResolution / 1.69), 0) if ($formWidth -lt 440) { $formWidth = 440 } if ($formHeight -lt 640) { $formHeight = 640 } # Create form object $form = New-Object -TypeName "System.Windows.Forms.Form" -Property @{ Width = $formWidth #440 Height = $formHeight #640 } # Create web browser object $web = New-Object -TypeName "System.Windows.Forms.WebBrowser" -Property @{ Url = $Url ClientSize = $form.ClientSize ScriptErrorsSuppressed = $true } #region Event actions # parse code or error message from URL, when Login is completed $web.Add_DocumentCompleted( { if ($web.Url.AbsoluteUri -match "error=[^&]*|code=[^&]*") { $form.Close() } } ) # Things to do when form is opened/shown $form.Add_Shown( { $form.BringToFront() $null = $form.Focus() $form.Activate() $web.Navigate($Url) $form.Text = $web.DocumentTitle } ) # make form resizeable $form.Add_Resize( { $web.ClientSize = $form.ClientSize $form.Text = $web.DocumentTitle } ) #endregion Event actions # Add browser to windows form $form.Controls.Add($web) # Show form to the user $null = $form.ShowDialog() # Get result from uri (query string within the uri) $queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query) $output = @{} foreach ($key in $queryOutput.Keys) { $output["$key"] = $queryOutput[$key] } # output result [pscustomobject]$output } } function Test-TokenScope { <# .SYNOPSIS Test for scopes existence on a Token .DESCRIPTION Test for existence on scopes (permissions) in a Token Helper function used for internal commands. .PARAMETER Token The Token to test. .PARAMETER Scope The scope(s) the check for existence. .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .EXAMPLE PS C:\> Test-TokenScope -User $Token -Scope "Mail.Read" Test if the specified Token contains scope "Mail.Read" #> [OutputType([bool])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [MSGraph.Core.AzureAccessToken] $Token, [Parameter(Mandatory = $true)] [string[]] $Scope, [String] $FunctionName = $MyInvocation.MyCommand ) begin { $Status = $false } process { $Token = Resolve-Token -Token $Token -FunctionName $MyInvocation.MyCommand Write-PSFMessage -Level VeryVerbose -Message "Validating token scope ($([String]::Join(", ",$Token.Scope))) against specified scope(s) ($([String]::Join(", ",$Scope)))" -Tag "Authenication" -FunctionName $FunctionName foreach ($scopeName in $Scope) { foreach ($tokenScope in $Token.Scope) { if ($tokenScope -like "$scopeName*") { Write-PSFMessage -Level Debug -Message "Token has appropriate scope ($($scopeName))" -Tag "Authenication" -FunctionName $FunctionName $Status = $true } } } } end { $Status } } function Resolve-MailObjectFromString { <# .SYNOPSIS Resolves a name/id from a mail or folder parameter class .DESCRIPTION Resolves a name/id from a mail or folder parameter class to a full qualified mail or folder object and return the parameter class back. Helper function used for internal commands. .PARAMETER Object The mail or folder object .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER NoNameResolving If specified, there will be no checking on names. Only Id will be resolved. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> Resolve-MailObjectFromString -Object $MailFolder -User $User -Token $Token -Function $MyInvocation.MyCommand Resolves $MailFolder into a legitimate user targeting string element. #> [OutputType()] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] param ( $Object, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $NoNameResolving, [String] $FunctionName = $MyInvocation.MyCommand ) # variable definition $invokeParam = @{ "User" = $User "Token" = $Token } # check input object type if ($Object.psobject.TypeNames[0] -like "MSGraph.Exchange.Mail.FolderParameter") { $type = "Folder" $typeNamespace = "MSGraph.Exchange.Mail" $nounPreFix = "MgaMail" $parameterName = "InputObject" } elseif ($Object.psobject.TypeNames[0] -like "MSGraph.Exchange.Mail.MessageParameter") { $type = "Message" $typeNamespace = "MSGraph.Exchange.Mail" $nounPreFix = "MgaMail" $parameterName = "InputObject" } elseif ($Object.psobject.TypeNames[0] -like "MSGraph.Exchange.Category.CategoryParameter") { $type = "Category" $typeNamespace = "MSGraph.Exchange.Category" $nounPreFix = "MgaExch" if ($Object.Id) { $parameterName = "Id" } else { $parameterName = "Name" } } else { $msg = "Object '$($Object)' is not valid. Must be one of: 'MSGraph.Exchange.Mail.FolderParameter', 'MSGraph.Exchange.Mail.MessageParameter', 'MSGraph.Exchange.Category.CategoryParameter'." Stop-PSFFunction -Message $msg -Tag "InputValidation" -FunctionName $FunctionName -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg)) } Write-PSFMessage -Level Debug -Message "Object '$($Object)' is qualified as a $($type)" -Tag "InputValidation" -FunctionName $FunctionName # Resolve the object if ($Object.Id -and (Test-MgaMailObjectId -Id $Object.Id -Type $type -FunctionName $FunctionName)) { Write-PSFMessage -Level Debug -Message "Going to resolve '$($Object)' with Id" -Tag "InputValidation" -FunctionName $FunctionName $invokeParam.Add($parameterName, $Object.Id) $output = .("Get-" + $nounPreFix + $type) @invokeParam } elseif ($Object.Name -and (-not $NoNameResolving)) { Write-PSFMessage -Level Debug -Message "Going to resolve '$($Object)' with name" -Tag "InputValidation" -FunctionName $FunctionName $invokeParam.Add($parameterName, $Object.Name) $invokeParam.Add("ErrorAction", "Stop") $output = .("Get-" + $nounPreFix + $type) @invokeParam } else { # not valid, end function without output Write-PSFMessage -Level Warning -Message "The specified input string seams not to be a valid Id. Skipping object '$($Object)'" -Tag "InputValidation" -FunctionName $FunctionName return } # output the result if ($output) { New-Object -TypeName "$($typeNamespace).$($type)Parameter" -ArgumentList $output } } function Resolve-UserInMailObject { <# .SYNOPSIS Resolves the user from a mail or folder parameter class .DESCRIPTION Resolves the user a mail or folder parameter class and compares against the specified user. If user in object is different, from the specified user, the user from the object is put out. Helper function used for internal commands. .PARAMETER Object The mail or folder object .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER ShowWarning If specified, there will be no warning output on the console. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> Resolve-UserInMailObject -Object $MailFolder -User $User -Function $MyInvocation.MyCommand Resolves the user from a mail or folder parameter class. #> [OutputType()] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] param ( $Object, [string] $User, [switch] $ShowWarning, [String] $FunctionName = $MyInvocation.MyCommand ) # check input object type [bool]$failed = $false switch ($Object.psobject.TypeNames[0]) { "MSGraph.Exchange.Mail.FolderParameter" { $namespace = "MSGraph.Exchange.Mail" $Type = "Folder" } "MSGraph.Exchange.Mail.MessageParameter" { $namespace = "MSGraph.Exchange.Mail" $Type = "Message" } "MSGraph.Exchange.Category.CategoryParameter" { $namespace = "MSGraph.Exchange.Category" $Type = "Category" } "MSGraph.Exchange.MailboxSetting.MailboxSettingParameter" { $namespace = "MSGraph.Exchange.MailboxSetting" $Type = "MailboxSettings" } Default { $failed = $true } } if($failed) { $msg = "Object '$($Object)' is not valid. Must be one of: 'MSGraph.Exchange.*.*Parameter' object types. Developers mistake!" Stop-PSFFunction -Message $msg -Tag "InputValidation" -FunctionName $FunctionName -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg)) } Write-PSFMessage -Level Debug -Message "Object '$($Object)' is qualified as a $($Type)" -Tag "InputValidation" -FunctionName $FunctionName if ($ShowWarning) { $level = @{ Level = "Warning" } } else { $level = @{ Level = "Verbose" } } $output = "" # Resolve the object if ($User -and ($Object.TypeName -like "$($namespace).$($Type)") -and ($User -notlike $Object.InputObject.User)) { Write-PSFMessage @Level -Message "Individual user specified! User from $($Type)Object ($($Object.InputObject.User)) will take precedence on specified user ($($User))!" -Tag "InputValidation" -FunctionName $FunctionName $output = $Object.InputObject.User } elseif ((-not $User) -and ($Object.TypeName -like "$($namespace).$($Type)")) { $output = $Object.InputObject.User } # output the result $output } function Test-MgaMailObjectId { <# .SYNOPSIS Test for valid object ID length on folders or message objects .DESCRIPTION Validates the length of an Id for objects in Exchange Online Helper function used for internal commands. .PARAMETER Id The Id to test. .PARAMETER Type The expected type of the object .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .EXAMPLE PS C:\> Test-MgaMailObjectId -Id $Id -Scope Folder Test if the specified $Id is a folder #> [OutputType([bool])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Id, [Parameter(Mandatory = $true)] [validateset("Folder", "Message", "Category")] [string] $Type, [String] $FunctionName = $MyInvocation.MyCommand ) begin { $status = $false [guid]$guidId = [guid]::Empty } process { $Token = Resolve-Token -Token $Token -FunctionName $MyInvocation.MyCommand Write-PSFMessage -Level Debug -Message "Validating Id '$($Id)' for $($Type) length" -Tag "ValidateObjectId" -FunctionName $FunctionName switch ($Type) { "Folder" { if ($Id.Length -eq 120 -or $Id.Length -eq 104) { $status = $true } } "Message" { if ($Id.Length -eq 152 -or $Id.Length -eq 136) { $status = $true } } "Category" { if ( [guid]::TryParse($Id, [ref]$guidId) ) { $status = $true } } } } end { if ($status) { Write-PSFMessage -Level Debug -Message "Id has appropriate length ($($Id.Length)) to be a $($Type)." -Tag "ValidateObjectId" -FunctionName $FunctionName } $status } } function New-JsonAttachmentObject { <# .SYNOPSIS Creates a json attachment object for use in Microsoft Graph REST api .DESCRIPTION Creates a json attachment object for use in Microsoft Graph REST api Helper function used for internal commands. .PARAMETER Name The name of attachment. .PARAMETER Size The size in bytes of the attachment. .PARAMETER IsInline Set to true if this is an inline attachment. .PARAMETER LastModifiedDateTime The date and time when the attachment was last modified. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: '2014-01-01T00:00:00Z' .PARAMETER ContentType The content type of the attachment. .PARAMETER contentBytes The base64-encoded contents of the file. .PARAMETER contentLocation The Uniform Resource Identifier (URI) that corresponds to the location of the content of the attachment. .PARAMETER Item The attached message or event. Navigation property. .PARAMETER IsFolder Property indicates, wether the object is a folder or not. .PARAMETER Permission The stated permission on the reference attachment. .PARAMETER PreviewUrl The url the preview the reference attachment. .PARAMETER ProviderType Specifies what type of reference is it. .PARAMETER SourceUrl The Url where the reference attachment points to. .PARAMETER ThumbnailUrl The Url of the thumbnail for the reference attachment. .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/resources/attachment?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/resources/fileattachment?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/resources/itemattachment?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/resources/referenceattachment?view=graph-rest-1.0 .EXAMPLE PS C:\> New-JsonAttachmentObject Creates a json attachment object for use in Microsoft Graph REST api #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'FileAttachment')] [OutputType([String])] param ( [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $Name, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [int32] $Size, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [bool] $IsInline, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $LastModifiedDateTime, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $ContentType, [Parameter(ParameterSetName = 'FileAttachment')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $contentBytes, [Parameter(ParameterSetName = 'FileAttachment')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $contentLocation, [Parameter(ParameterSetName = 'ItemAttachment')] [psobject] $Item, [Parameter(ParameterSetName = 'ReferenceAttachment')] [String] $SourceUrl, [Parameter(ParameterSetName = 'ReferenceAttachment')] [String] $ProviderType, [Parameter(ParameterSetName = 'ReferenceAttachment')] [String] $ThumbnailUrl, [Parameter(ParameterSetName = 'ReferenceAttachment')] [String] $PreviewUrl, [Parameter(ParameterSetName = 'ReferenceAttachment')] [String] $Permission, [Parameter(ParameterSetName = 'ReferenceAttachment')] [bool] $IsFolder, [String] $FunctionName ) begin { } process { Write-PSFMessage -Level Debug -Message "Create attachment JSON object" -Tag "ParameterSetHandling" #region variable definition $boundParameters = @() $bodyHash = [ordered]@{} $variableNames = @("Name", "Size", "IsInline", "LastModifiedDateTime", "ContentType") switch ($PSCmdlet.ParameterSetName) { 'FileAttachment' { $variableNames = $variableNames + @("contentBytes", "contentLocation") } 'ItemAttachment' { $variableNames = $variableNames + @("item") } 'ReferenceAttachment' { $variableNames = $variableNames + @("SourceUrl", "ProviderType", "ThumbnailUrl", "PreviewUrl", "Permission", "IsFolder") } } #endregion variable definition #region Parsing string and boolean parameters to json data parts Write-PSFMessage -Level VeryVerbose -Message "Parsing parameters to json data parts ($([string]::Join(", ", $variableNames)))" -Tag "ParameterParsing" -FunctionName $FunctionName $bodyHash.Add("@odata.type", """#microsoft.graph.$($PSCmdlet.ParameterSetName)""") foreach ($variableName in $variableNames) { if (Test-PSFParameterBinding -ParameterName $variableName) { $boundParameters = $boundParameters + $variableName Write-PSFMessage -Level Debug -Message "Parsing parameter $($variableName)" -Tag "ParameterParsing" $bodyHash.Add($variableName, ((Get-Variable $variableName -Scope 0).Value | ConvertTo-Json)) } } #endregion Parsing string and boolean parameters to json data parts # Put parameters (JSON Parts) into a valid JSON-object together and output the result $bodyJSON = Merge-HashToJson $bodyHash $bodyJSON } end { } } function New-MgaAttachmentObject { <# .SYNOPSIS Create new Attachment object .DESCRIPTION Create new Attachment object Helper function used for internal commands. .PARAMETER RestData The RestData object containing the data for the new message object. .PARAMETER ParentObject The ParentObject object where the attachment came from. .PARAMETER ApiVersion The version used for queries in Microsoft Graph connection .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER User The user to execute this under. Defaults to the user the token belongs to. .PARAMETER Token The access token to use to connect. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> New-MgaAttachmentObject -RestData $output Create a MSGraph.Exchange.Attachment.* object from data in variable $output #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( $RestData, $ParentObject, $ApiVersion, [Int64] $ResultSize, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [String] $FunctionName ) $outputHash = [ordered]@{ Id = $RestData.Id Name = $RestData.Name AttachmentType = [MSGraph.Exchange.Attachment.AttachmentTypes]$RestData.'@odata.type'.split(".")[($RestData.'@odata.type'.split(".").count - 1)] ContentType = $RestData.ContentType IsInline = $RestData.isInline LastModifiedDateTime = $RestData.LastModifiedDateTime Size = $RestData.Size User = $RestData.user ParentObject = $ParentObject BaseObject = $RestData } switch ($RestData.'@odata.type') { '#microsoft.graph.itemAttachment' { $invokeParam = @{ "Field" = "messages/$($ParentObject.Id)/attachments/$($RestData.id)/?`$expand=microsoft.graph.itemattachment/item" "Token" = $Token "User" = $User "ResultSize" = $ResultSize "ApiVersion" = $ApiVersion "FunctionName" = $FunctionName } $itemData = Invoke-MgaRestMethodGet @invokeParam $outputHash.BaseObject = $itemData $outputHash.Id = $itemData.id $outputHash.Add("Item", $itemData.Item) New-Object -TypeName MSGraph.Exchange.Attachment.ItemAttachment -Property $outputHash } '#microsoft.graph.referenceAttachment' { $outputHash.Add("SourceUrl", [uri]$RestData.SourceUrl) $outputHash.Add("ProviderType", [MSGraph.Exchange.Attachment.ReferenceAttachmentProvider]$RestData.ProviderType) $outputHash.Add("ThumbnailUrl", [uri]$RestData.ThumbnailUrl) $outputHash.Add("PreviewUrl", [uri]$RestData.PreviewUrl) $outputHash.Add("Permission", [MSGraph.Exchange.Attachment.referenceAttachmentPermission]$RestData.Permission) $outputHash.Add("IsFolder", [bool]::Parse($RestData.IsFolder)) New-Object -TypeName MSGraph.Exchange.Attachment.ReferenceAttachment -Property $outputHash } '#microsoft.graph.fileAttachment' { $outputHash.Add("ContentId", $RestData.ContentId) $outputHash.Add("ContentLocation", $RestData.ContentLocation) $outputHash.Add("ContentBytes", [system.convert]::FromBase64String($RestData.contentBytes)) New-Object -TypeName MSGraph.Exchange.Attachment.FileAttachment -Property $outputHash } Default { New-Object -TypeName MSGraph.Exchange.Attachment.Attachment -Property $outputHash } } } function New-JsonMailObject { <# .SYNOPSIS Creates a json message object for use in Microsoft Graph REST api .DESCRIPTION Creates a json message object for use in Microsoft Graph REST api Helper function used for internal commands. .PARAMETER Subject The subject of the new message. .PARAMETER Sender The account that is actually used to generate the message. (Updatable only when sending a message from a shared mailbox or sending a message as a delegate. In any case, the value must correspond to the actual mailbox used.) .PARAMETER From The mailbox owner and sender of the message. Must correspond to the actual mailbox used. .PARAMETER ToRecipients The To recipients for the message. .PARAMETER CCRecipients The Cc recipients for the message. .PARAMETER BCCRecipients The Bcc recipients for the message. .PARAMETER ReplyTo The email addresses to use when replying. .PARAMETER Body The body of the message. .PARAMETER Categories The categories associated with the message. .PARAMETER Importance The importance of the message. The possible values are: Low, Normal, High. .PARAMETER InferenceClassification The classification of the message for the user, based on inferred relevance or importance, or on an explicit override. The possible values are: focused or other. .PARAMETER InternetMessageId The message ID in the format specified by RFC2822. .PARAMETER IsDeliveryReceiptRequested Indicates whether a delivery receipt is requested for the message. .PARAMETER IsReadReceiptRequested Indicates whether a read receipt is requested for the message. .PARAMETER IsRead Indicates whether the message has been read. .PARAMETER Comment The comment in a body of a forwarded message. Only used when forwarding messages. .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 .EXAMPLE PS C:\> New-JsonMailObject Creates a json message object for use in Microsoft Graph REST api #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] [OutputType([String])] param ( [bool] $IsRead, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $Subject, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $Sender, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $From, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $ToRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $CCRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $BCCRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $ReplyTo, [String] $Body, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [String[]] $Categories, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [ValidateSet($null, "", "Low", "Normal", "High")] [String] $Importance, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [ValidateSet($null, "", "focused", "other")] [String] $InferenceClassification, [String] $InternetMessageId, [bool] $IsDeliveryReceiptRequested, [bool] $IsReadReceiptRequested, [String] $Comment, [String] $FunctionName ) begin { #region variable definition $boundParameters = @() $mailAddressNames = @("sender", "from", "toRecipients", "ccRecipients", "bccRecipients", "replyTo") #endregion variable definition # parsing mailAddress parameter strings to mailaddress objects (if not empty) foreach ($Name in $mailAddressNames) { if (Test-PSFParameterBinding -ParameterName $name) { New-Variable -Name "$($name)Addresses" -Force -Scope 0 if ((Get-Variable -Name $Name -Scope 0).Value) { try { $mailAddress = (Get-Variable -Name $Name -Scope 0).Value | ForEach-Object { [mailaddress]$_ } -ErrorAction Stop -ErrorVariable parseError Set-Variable -Name "$($name)Addresses" -Value $mailAddress Remove-Variable mailaddress -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction Ignore } catch { Stop-PSFFunction -Message "Unable to parse $($name) to a mailaddress. String should be 'name@domain.topleveldomain' or 'displayname name@domain.topleveldomain'. Error: $($parseError[0].Exception.Message)" -Tag "ParameterParsing" -Category InvalidData -EnableException $true -Exception $parseError[0].Exception -FunctionName $FunctionName } } } } } process { $bodyHash = @{} Write-PSFMessage -Level Debug -Message "Create message JSON object" -Tag "ParameterSetHandling" #region Parsing string and boolean parameters to json data parts $names = @("Comment", "IsRead", "Subject", "Categories", "Importance", "InferenceClassification", "InternetMessageId", "IsDeliveryReceiptRequested", "IsReadReceiptRequested") Write-PSFMessage -Level VeryVerbose -Message "Parsing string and boolean parameters to json data parts ($([string]::Join(", ", $names)))" -Tag "ParameterParsing" foreach ($name in $names) { if (Test-PSFParameterBinding -ParameterName $name) { $boundParameters = $boundParameters + $name Write-PSFMessage -Level Debug -Message "Parsing text parameter $($name)" -Tag "ParameterParsing" $bodyHash.Add($name, ((Get-Variable $name -Scope 0).Value | ConvertTo-Json)) } } if ($Body) { $bodyHash.Add("Body", ([MSGraph.Exchange.Mail.MessageBody]$Body | ConvertTo-Json)) } #endregion Parsing string and boolean parameters to json data parts #region Parsing mailaddress parameters to json data parts Write-PSFMessage -Level VeryVerbose -Message "Parsing mailaddress parameters to json data parts ($([string]::Join(", ", $mailAddressNames)))" -Tag "ParameterParsing" foreach ($name in $mailAddressNames) { if (Test-PSFParameterBinding -ParameterName $name) { $boundParameters = $boundParameters + $name Write-PSFMessage -Level Debug -Message "Parsing mailaddress parameter $($name)" -Tag "ParameterParsing" $addresses = (Get-Variable -Name "$($name)Addresses" -Scope 0).Value if ($addresses) { # build valid mail address object, if address is specified [array]$addresses = foreach ($item in $addresses) { [PSCustomObject]@{ emailAddress = [PSCustomObject]@{ address = $item.Address name = $item.DisplayName } } } } else { # place an empty mail address object in, if no address is specified (this will clear the field in the message) [array]$addresses = [PSCustomObject]@{ emailAddress = [PSCustomObject]@{ address = "" name = "" } } } if ($name -in @("toRecipients", "ccRecipients", "bccRecipients", "replyTo")) { # these kind of objects need to be an JSON array if ($addresses.Count -eq 1) { # hardly format JSON object as an array, because ConvertTo-JSON will output a single object-json-string on an array with count 1 (PSVersion 5.1.17134.407 | PSVersion 6.1.1) $bodyHash.Add($name, ("[" + ($addresses | ConvertTo-Json) + "]") ) } else { $bodyHash.Add($name, ($addresses | ConvertTo-Json) ) } } else { $bodyHash.Add($name, ($addresses | ConvertTo-Json) ) } } } #endregion Parsing mailaddress parameters to json data parts # Put parameters (JSON Parts) into a valid JSON-object together and output the result $bodyJSON = Merge-HashToJson $bodyHash $bodyJSON } end { } } function New-MgaMailFolderObject { <# .SYNOPSIS Create new FolderObject .DESCRIPTION Create new FolderObject Helper function used for internal commands. .PARAMETER RestData The RestData object containing the data for the new message object. .PARAMETER Level The hierarchy level of the folder. 1 means the folder is a root folder. .PARAMETER ParentFolder If known/ existing, the parent folder object of the folder object to create. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> New-MgaMailFolderObject -RestData $output -Level $Level -ParentFolder $ParentFolder -FunctionName $MyInvocation.MyCommand Create a MSGraph.Exchange.Mail.Folder object from data in variable $output #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [OutputType([MSGraph.Exchange.Mail.Folder])] [CmdletBinding()] param ( $RestData, [MSGraph.Exchange.Mail.FolderParameter] $ParentFolder, [int] $Level, [String] $FunctionName ) if ((-not $Level) -and $ParentFolder) { $Level = $ParentFolder.InputObject.HierarchyLevel + 1 } elseif ((-not $Level) -and (-not $ParentFolder)) { $Level = 1 } $hash = @{ Id = $RestData.Id DisplayName = $RestData.DisplayName ParentFolderId = $RestData.ParentFolderId ChildFolderCount = $RestData.ChildFolderCount UnreadItemCount = $RestData.UnreadItemCount TotalItemCount = $RestData.TotalItemCount User = $RestData.User HierarchyLevel = $Level } if ($ParentFolder) { $hash.Add("ParentFolder", $ParentFolder.InputObject) } $OutputObject = New-Object -TypeName MSGraph.Exchange.Mail.Folder -Property $hash $OutputObject } function New-MgaMailMessageObject { <# .SYNOPSIS Create new MessageObject .DESCRIPTION Create new MessageObject Helper function used for internal commands. .PARAMETER RestData The RestData object containing the data for the new message object. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> New-MgaMailMessageObject -RestData $output Create a MSGraph.Exchange.Mail.Message object from data in variable $output #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [OutputType([MSGraph.Exchange.Mail.Message])] [CmdletBinding()] param ( $RestData, [String] $FunctionName ) #region Handle dates if ($RestData.createdDateTime.psobject.TypeNames[0] -like "System.DateTime") { $createdDateTime = $RestData.createdDateTime } else { $createdDateTime = [datetime]::Parse($RestData.createdDateTime) } if ($RestData.lastModifiedDateTime.psobject.TypeNames[0] -like "System.DateTime") { $lastModifiedDateTime = $RestData.lastModifiedDateTime } else { $lastModifiedDateTime = [datetime]::Parse($RestData.lastModifiedDateTime) } if ($RestData.receivedDateTime.psobject.TypeNames[0] -like "System.DateTime") { $receivedDateTime = $RestData.receivedDateTime } else { if($RestData.receivedDateTime) { $receivedDateTime = [datetime]::Parse($RestData.receivedDateTime) } else { $receivedDateTime = "" } } if ($RestData.sentDateTime.psobject.TypeNames[0] -like "System.DateTime") { $sentDateTime = $RestData.sentDateTime } else { if($RestData.sentDateTime) { $sentDateTime = [datetime]::Parse($RestData.sentDateTime) } else { $sentDateTime = "" } } #endregion Handle dates $hash = [ordered]@{ BaseObject = $RestData Subject = $RestData.subject Body = $RestData.body BodyPreview = $RestData.bodyPreview Categories = $RestData.categories ChangeKey = $RestData.changeKey ConversationId = $RestData.conversationId CreatedDateTime = $createdDateTime Flag = $RestData.flag.flagStatus HasAttachments = $RestData.hasAttachments Id = $RestData.id Importance = $RestData.importance InferenceClassification = $RestData.inferenceClassification InternetMessageId = $RestData.internetMessageId IsDeliveryReceiptRequested = $RestData.isDeliveryReceiptRequested IsDraft = $RestData.isDraft IsRead = $RestData.isRead isReadReceiptRequested = $RestData.isReadReceiptRequested lastModifiedDateTime = $lastModifiedDateTime MeetingMessageType = $RestData.meetingMessageType ParentFolderId = $RestData.parentFolderId WebLink = $RestData.webLink User = $RestData.User } if ($RestData.receivedDateTime) { $hash.Add("ReceivedDateTime", $receivedDateTime) } if ($RestData.sentDateTime) { $hash.Add("SentDateTime", $sentDateTime) } if ($RestData.from.emailAddress) { if ($RestData.from.emailAddress.name -like $RestData.from.emailAddress.address) { # if emailaddress is same in address and in name field, only use address field $from = $RestData.from.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue } else { $from = $RestData.from.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue } $hash.Add("from", $from) } if ($RestData.Sender.emailAddress) { if ($RestData.Sender.emailAddress.name -like $RestData.Sender.emailAddress.address) { # if emailaddress is same in address and in name field, only use address field $senderaddress = $RestData.Sender.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue } else { $senderaddress = $RestData.Sender.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue } $hash.Add("Sender", $senderaddress) } if ($RestData.bccRecipients.emailAddress) { if ($RestData.bccRecipients.emailAddress.name -like $RestData.bccRecipients.emailAddress.address) { # if emailaddress is same in address and in name field, only use address field [array]$bccRecipients = $RestData.bccRecipients.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue } else { [array]$bccRecipients = $RestData.bccRecipients.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue } $hash.Add("bccRecipients", [array]$bccRecipients) } if ($RestData.ccRecipients.emailAddress) { if ($RestData.ccRecipients.emailAddress.name -like $RestData.ccRecipients.emailAddress.address) { # if emailaddress is same in address and in name field, only use address field [array]$ccRecipients = $RestData.ccRecipients.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue } else { [array]$ccRecipients = $RestData.ccRecipients.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue } $hash.Add("ccRecipients", [array]$ccRecipients) } if ($RestData.replyTo.emailAddress) { if ($RestData.replyTo.emailAddress.name -like $RestData.replyTo.emailAddress.address) { # if emailaddress is same in address and in name field, only use address field [array]$replyTo = $RestData.replyTo.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue } else { [array]$replyTo = $RestData.replyTo.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue } $hash.Add("replyTo", [array]$replyTo) } if ($RestData.toRecipients.emailAddress) { if ($RestData.toRecipients.emailAddress.name -like $RestData.toRecipients.emailAddress.address) { # if emailaddress is same in address and in name field, only use address field [array]$toRecipients = $RestData.toRecipients.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue } else { [array]$toRecipients = $RestData.toRecipients.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue } $hash.Add("toRecipients", [array]$toRecipients) } $messageOutputObject = New-Object -TypeName MSGraph.Exchange.Mail.Message -Property $hash $messageOutputObject } function New-JsonAutomaticRepliesSettingFraction { <# .SYNOPSIS Creates a json object from AutomaticRepliesSetting object .DESCRIPTION Creates a json object from AutomaticRepliesSetting object used for Microsoft Graph REST api Helper function used for internal commands. .PARAMETER AutomaticRepliesSetting The object to convert to json .EXAMPLE PS C:\> New-JsonAutomaticRepliesSettingFraction -AutomaticRepliesSetting $automaticRepliesSetting Creates a json object from AutomaticRepliesSetting object used for Microsoft Graph REST api #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] [OutputType([String])] param ( [MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting] $AutomaticRepliesSetting ) $automaticRepliesSettingHash = [ordered]@{ "status" = $AutomaticRepliesSetting.Status.ToString() "externalAudience" = $AutomaticRepliesSetting.ExternalAudience.ToString() "internalReplyMessage" = $AutomaticRepliesSetting.InternalReplyMessage "externalReplyMessage" = $AutomaticRepliesSetting.ExternalReplyMessage "scheduledStartDateTime" = [ordered]@{ #"dateTime" = ($AutomaticRepliesSetting.ScheduledStartDateTimeUTC.DateTime | Get-Date -Format s) "dateTime" = $AutomaticRepliesSetting.ScheduledStartDateTimeUTC.DateTime.ToString("s") # "s" means sortable date: 2000-01-01T01:01:01(.010001) "timeZone" = $AutomaticRepliesSetting.ScheduledStartDateTimeUTC.TimeZone } "scheduledEndDateTime" = [ordered]@{ #"dateTime" = ($AutomaticRepliesSetting.ScheduledEndDateTimeUTC.DateTime | Get-Date -Format s) "dateTime" = $AutomaticRepliesSetting.ScheduledEndDateTimeUTC.DateTime.ToString("s") # "s" means sortable date: 2000-01-01T01:01:01(.010001) "timeZone" = $AutomaticRepliesSetting.ScheduledEndDateTimeUTC.TimeZone } } $automaticRepliesSettingObject = New-Object psobject -Property $automaticRepliesSettingHash $automaticRepliesSettingJSON = ConvertTo-Json -InputObject $automaticRepliesSettingObject $automaticRepliesSettingJSON } function New-JsonLanguageSettingFraction { <# .SYNOPSIS Creates a json object from LocaleInfoSetting (LanguageSetting) object .DESCRIPTION Creates a json object from LocaleInfoSetting (LanguageSetting) object used for Microsoft Graph REST api Helper function used for internal commands. .PARAMETER LanguageSetting The object to convert to json .EXAMPLE PS C:\> New-JsonLanguageSettingFraction -LanguageSetting $languageSetting Creates a json object from LanguageSetting object used for Microsoft Graph REST api #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] [OutputType([String])] param ( [MSGraph.Exchange.MailboxSetting.LocaleInfoSetting] $LanguageSetting ) $languageSettingHash = [ordered]@{ "locale" = $LanguageSetting.Locale.ToString() #"displayName" = $LanguageSetting.DisplayName # causes errors on rest patch call } $languageSettingObject = New-Object psobject -Property $languageSettingHash $languageSettingJSON = ConvertTo-Json -InputObject $languageSettingObject $languageSettingJSON } function New-JsonMailboxSettingObject { <# .SYNOPSIS Creates a json mailsettings object for use in Microsoft Graph REST api .DESCRIPTION Creates a json mailsettings object for use in Microsoft Graph REST api Helper function used for internal commands. .PARAMETER SettingObject The object to be converted into JSON format containing the data for the new message object. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER FunctionName Name of the higher function which is calling this function. (Just used for logging reasons) .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/user-update-mailboxsettings?view=graph-rest-1.0 .EXAMPLE PS C:\> New-JsonMailboxSettingObject -SettingObject $settingObject -User $user -FunctionName $MyInvocation.MyCommand Creates a json MailboxSetting object for use in Microsoft Graph REST api #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] [OutputType([String])] param ( [MSGraph.Exchange.MailboxSetting.MailboxSettingParameter] $SettingObject, [string] $User, [String] $FunctionName ) begin { } process { Write-PSFMessage -Level Debug -Message "Working on '$($SettingObject)' to create mailboxSetting JSON object" -Tag "ParameterSetHandling" #region variable definition $bodyHash = [ordered]@{} #endregion variable definition #region Parsing input to json data parts # set field @odata.context - required if ($SettingObject.InputObject.BaseObject.'@odata.context') { $context = $SettingObject.InputObject.BaseObject.'@odata.context' if ($context -match '\/mailboxSettings\/\w*$') { $context = $context.Replace($Matches.Values, "/mailboxsettings") } Remove-Variable -Name Matches -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore } else { $apiConnection = Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com' $apiVersion = Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0' $resolvedUser = Resolve-UserString -User $User -ContextData $context = "$($apiConnection)/$($apiVersion)/`$metadata#$($resolvedUser)/mailboxsettings" Remove-Variable -Name apiConnection, apiVersion -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore } $bodyHash.Add('@odata.context', """$context""") # depending on type of object switch ($SettingObject.TypeName) { 'MSGraph.Exchange.MailboxSetting.MailboxSettings' { # set archive folder Write-PSFMessage -Level VeryVerbose -Message "Prepare setting archive folder to '$($SettingObject.InputObject.ArchiveFolder)'" -Tag "CreateJSON" -FunctionName $FunctionName $bodyHash.Add('archiveFolder', ($SettingObject.InputObject.ArchiveFolder.Id | ConvertTo-Json)) # set time zone Write-PSFMessage -Level VeryVerbose -Message "Prepare setting timezone to '$($SettingObject.InputObject.TimeZone.Id)'" -Tag "CreateJSON" -FunctionName $FunctionName $bodyHash.Add('timeZone', ($SettingObject.InputObject.TimeZone.Id | ConvertTo-Json)) #$bodyHash.Add('timeZone', ('"' + "W. Europe Standard Time" + '"')) # set auto reply Write-PSFMessage -Level VeryVerbose -Message "Prepare setting autoreply to '$($SettingObject.InputObject.automaticRepliesSetting.Status)'" -Tag "CreateJSON" -FunctionName $FunctionName $automaticRepliesSettingJSON = New-JsonAutomaticRepliesSettingFraction -AutomaticRepliesSetting $SettingObject.InputObject.automaticRepliesSetting $bodyHash.Add('automaticRepliesSetting', $automaticRepliesSettingJSON) # set language Write-PSFMessage -Level VeryVerbose -Message "Prepare setting language to '$($SettingObject.InputObject.Language)'" -Tag "CreateJSON" -FunctionName $FunctionName $languageSettingJSON = New-JsonLanguageSettingFraction -LanguageSetting $SettingObject.InputObject.Language $bodyHash.Add('language', $languageSettingJSON) # set working hours Write-PSFMessage -Level VeryVerbose -Message "Prepare setting workingHours to '$($SettingObject.InputObject.WorkingHours)'" -Tag "CreateJSON" -FunctionName $FunctionName $workingHoursSettingJSON = New-JsonWorkingHoursSettingFraction -WorkingHoursSetting $SettingObject.InputObject.WorkingHours $bodyHash.Add('workingHours', $workingHoursSettingJSON) } 'MSGraph.Exchange.Mail.Folder' { # set archive folder Write-PSFMessage -Level VeryVerbose -Message "Prepare setting archive folder to '$($SettingObject.InputObject)'" -Tag "CreateJSON" -FunctionName $FunctionName $bodyHash.Add('archiveFolder', ($SettingObject.InputObject.Id | ConvertTo-Json)) } 'System.TimeZoneInfo' { # set time zone Write-PSFMessage -Level VeryVerbose -Message "Prepare setting timezone to '$($SettingObject.InputObject.Id)'" -Tag "CreateJSON" -FunctionName $FunctionName $bodyHash.Add('timeZone', ($SettingObject.InputObject.Id | ConvertTo-Json)) } 'MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting' { # set auto reply Write-PSFMessage -Level VeryVerbose -Message "Prepare setting autoreply to '$($SettingObject.InputObject.Status)'" -Tag "CreateJSON" -FunctionName $FunctionName $automaticRepliesSettingJSON = New-JsonAutomaticRepliesSettingFraction -AutomaticRepliesSetting $SettingObject.InputObject $bodyHash.Add('automaticRepliesSetting', $automaticRepliesSettingJSON) } 'MSGraph.Exchange.MailboxSetting.LocaleInfoSetting' { # set language Write-PSFMessage -Level VeryVerbose -Message "Prepare setting language to '$($SettingObject.InputObject)'" -Tag "CreateJSON" -FunctionName $FunctionName $languageSettingJSON = New-JsonLanguageSettingFraction -LanguageSetting $SettingObject.InputObject $bodyHash.Add('language', $languageSettingJSON) } 'MSGraph.Exchange.MailboxSetting.WorkingHoursSetting' { # set working hours Write-PSFMessage -Level VeryVerbose -Message "Prepare setting workingHours to '$($SettingObject.InputObject)'" -Tag "CreateJSON" -FunctionName $FunctionName $workingHoursSettingJSON = New-JsonWorkingHoursSettingFraction -WorkingHoursSetting $SettingObject.InputObject $bodyHash.Add('workingHours', $workingHoursSettingJSON) } Default { Stop-PSFFunction -Message "Unhandled type ($($SettingObject.TypeName)) of SettingObject. Developer mistake!" -EnableException $true -Category InvalidType -FunctionName $MyInvocation.MyCommand } } #endregion Parsing input to json data parts # Put parameters (JSON Parts) into a valid JSON-object and output the result $bodyJSON = Merge-HashToJSON $bodyHash $bodyJSON } end { } } function New-JsonWorkingHoursSettingFraction { <# .SYNOPSIS Creates a json object from WorkingHoursSetting object .DESCRIPTION Creates a json object from WorkingHoursSetting object used for Microsoft Graph REST api Helper function used for internal commands. .PARAMETER WorkingHoursSetting The object to convert to json .EXAMPLE PS C:\> New-JsonWorkingHoursSettingFraction -WorkingHoursSetting $workingHoursSetting Creates a json object from WorkingHoursSetting object used for Microsoft Graph REST api #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')] [OutputType([String])] param ( [MSGraph.Exchange.MailboxSetting.WorkingHoursSetting] $WorkingHoursSetting ) $workingHoursSettingHash = [ordered]@{ "daysOfWeek" = [array]$WorkingHoursSetting.DaysOfWeek.ForEach( {$_.ToString()} ) "startTime" = $WorkingHoursSetting.StartTime.ToString("HH:mm:ss.fffffff") "endTime" = $WorkingHoursSetting.EndTime.ToString("HH:mm:ss.fffffff") "timeZone" = @{ "name" = $WorkingHoursSetting.TimeZone.ToString() } } $workingHoursSettingObject = New-Object psobject -Property $workingHoursSettingHash $workingHoursSettingJSON = ConvertTo-Json -InputObject $workingHoursSettingObject $workingHoursSettingJSON } function New-MgaMailboxSettingObject { <# .SYNOPSIS Create new mailboxSettings object .DESCRIPTION Create new mailboxSettings object Helper function used for internal commands. .PARAMETER RestData The RestData object containing the data for the new message object. .PARAMETER Type The type of the settings object to be created. .PARAMETER User The user to execute this under. Defaults to the user the token belongs to. .PARAMETER Token The access token to use to connect. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> New-MgaMailboxSettingObject -RestData $output -Type MailboxSettings Create a MSGraph.Exchange.MailboxSetting.MailboxSettings object from data in variable $output #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")] [CmdletBinding()] param ( $RestData, [String] $Type, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [String] $FunctionName ) Write-PSFMessage -Level Debug -Message "Create $($Type) mailbox Setting object" -Tag "CreateObject" if ($Type -notlike "TimeZoneSetting" -and $Type -notlike "ArchiveFolderSetting") { $name = [System.Web.HttpUtility]::UrlDecode(([uri]$RestData.'@odata.context').Fragment).TrimStart("#") $outputHash = [ordered]@{ Name = $name User = $RestData.user BaseObject = $RestData } } switch ($Type) { {$_ -like 'AllSettings' -or $_ -like 'ArchiveFolderSetting'} { # create the full set of mailbox settings if ($RestData.archiveFolder) { try { $archivFolder = Get-MgaMailFolder -Name $RestData.archiveFolder -User $User -Token $Token -ErrorAction Stop } catch { Stop-PSFFunction -Message "Failed to get information about archiv folder on $($outputHash.Name)" -EnableException $true -Exception $_.Exception -Category ReadError -ErrorRecord $_ -Tag "QueryData" -FunctionName $FunctionName } if ($Type -like 'ArchiveFolderSetting') { return $archivFolder } else { $outputHash.Add("ArchiveFolder", $archivFolder) } } else { $archivFolder = "" } $timeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($RestData.timeZone) $outputHash.Add("TimeZone", $timeZone) $autoReplySetting = [MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting]::new( [MSGraph.Exchange.MailboxSetting.AutomaticRepliesStatus]$RestData.automaticRepliesSetting.Status, [MSGraph.Exchange.MailboxSetting.ExternalAudienceScope]$RestData.automaticRepliesSetting.ExternalAudience, $RestData.automaticRepliesSetting.ExternalReplyMessage.Trim([char]65279), $RestData.automaticRepliesSetting.internalReplyMessage.Trim([char]65279), [MSGraph.Exchange.DateTimeTimeZone]$RestData.automaticRepliesSetting.ScheduledStartDateTime, [MSGraph.Exchange.DateTimeTimeZone]$RestData.automaticRepliesSetting.ScheduledEndDateTime, "$($name)/automaticRepliesSetting" ) $outputHash.Add("AutomaticRepliesSetting", $autoReplySetting) $language = [MSGraph.Exchange.MailboxSetting.LocaleInfoSetting]::new( [cultureinfo]$RestData.language.locale, $RestData.language.displayName, "$($name)/language" ) $outputHash.Add("Language", $language) $workingHours = [MSGraph.Exchange.MailboxSetting.WorkingHoursSetting]::new( $RestData.WorkingHours.daysOfWeek.ForEach( {[dayOfWeek]$_}), [datetime]$RestData.WorkingHours.startTime, [datetime]$RestData.WorkingHours.endTime, [MSGraph.Exchange.TimeZoneBase]::new($RestData.WorkingHours.timeZone.name), "$($name)/workingHours" ) $outputHash.Add("WorkingHours", $workingHours) New-Object -TypeName MSGraph.Exchange.MailboxSetting.MailboxSettings -Property $outputHash } 'AutomaticReplySetting' { # create auto reply settings object if ($RestData.automaticRepliesSetting) { $autoReplySetting = $RestData.automaticRepliesSetting } else { $autoReplySetting = $RestData } $outputHash.Add("Status", [MSGraph.Exchange.MailboxSetting.AutomaticRepliesStatus]$autoReplySetting.Status) $outputHash.Add("ExternalAudience", [MSGraph.Exchange.MailboxSetting.ExternalAudienceScope]$autoReplySetting.ExternalAudience) $outputHash.Add("ExternalReplyMessage", $autoReplySetting.ExternalReplyMessage.Trim([char]65279)) $outputHash.Add("InternalReplyMessage", $autoReplySetting.internalReplyMessage.Trim([char]65279)) $outputHash.Add("ScheduledStartDateTimeUTC", [MSGraph.Exchange.DateTimeTimeZone]$autoReplySetting.ScheduledStartDateTime) $outputHash.Add("ScheduledEndDateTimeUTC", [MSGraph.Exchange.DateTimeTimeZone]$autoReplySetting.ScheduledEndDateTime) $output = New-Object -TypeName MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting -Property $outputHash $output Remove-Variable -Name autoReplySetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore } 'LanguageSetting' { # create language setting object if($RestData.language) { $languageSetting = $RestData.language } else { $languageSetting = $RestData } $outputHash.Add("Locale", [cultureinfo]$languageSetting.locale) $outputHash.Add("DisplayName", $languageSetting.displayName) $output = New-Object -TypeName MSGraph.Exchange.MailboxSetting.LocaleInfoSetting -Property $outputHash $output Remove-Variable -Name languageSetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore } 'TimeZoneSetting' { # create timeZone object if($RestData.timeZone) { $timeZoneSetting = $RestData.timeZone } else { $timeZoneSetting = $RestData } $output = [System.TimeZoneInfo]::FindSystemTimeZoneById($timeZoneSetting) $output Remove-Variable -Name timeZoneSetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore } 'WorkingHoursSetting' { # create workingHours object if($RestData.workingHours) { $workingHourSetting = $RestData.workingHours } else { $workingHourSetting = $RestData } $outputHash.Add("DaysOfWeek", $workingHourSetting.daysOfWeek.ForEach( {[dayOfWeek]$_})) $outputHash.Add("StartTime", [datetime]$workingHourSetting.startTime) $outputHash.Add("EndTime", [datetime]$workingHourSetting.endTime) $outputHash.Add("TimeZone", [MSGraph.Exchange.TimeZoneBase]::new($workingHourSetting.timeZone.name)) $output = New-Object -TypeName MSGraph.Exchange.MailboxSetting.WorkingHoursSetting -Property $outputHash $output Remove-Variable -Name workingHourSetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore } Default { Stop-PSFFunction -Message "Unable to output a valid MailboxSetting object, because of unhandled type '$($Type)'. Developer mistake." -EnableException $true -Category InvalidData -FunctionName $MyInvocation.MyCommand } } } function Get-MgaAccessTokenRegistered { <# .SYNOPSIS Output the registered access token .DESCRIPTION Output the registered access token .EXAMPLE PS C:\> Get-MgaRegisteredAccessToken Output the registered access token #> [CmdletBinding()] [Alias('Get-MgaRegisteredAccessToken')] param () if ($script:msgraph_Token) { $script:msgraph_Token } else { Write-PSFMessage -Level Host -Message "No access token registered." } } function Invoke-MgaRestMethodDelete { <# .SYNOPSIS Performs a REST DELETE against the graph API .DESCRIPTION Performs a REST DELETE against the graph API. Primarily used for internal commands. .PARAMETER Field The api child item under the username in the url of the api call. If this didn't make sense to you, you probably shouldn't be using this command ;) .PARAMETER User The user to execute this under. Defaults to the user the token belongs to. .PARAMETER Body JSON date as string to send as body on the REST call .PARAMETER ContentType Nature of the data in the body of an entity. Required. .PARAMETER ApiConnection The URI for the Microsoft Graph connection .PARAMETER ApiVersion The version used for queries in Microsoft Graph connection .PARAMETER Token The access token to use to connect. .PARAMETER Force If specified the user will not prompted on confirmation. .PARAMETER FunctionName Name of the higher function which is calling this function. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Invoke-MgaRestMethodDelete -Field "mailFolders/$($id)" Delete a mailfolder with the id stored in variable $id. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [Alias('Invoke-MgaDeleteMethod')] param ( [Parameter(Mandatory = $true)] [string] $Field, [string] $User, [String] $Body, [ValidateSet("application/json")] [String] $ContentType = "application/json", [String] $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'), [string] $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'), [MSGraph.Core.AzureAccessToken] $Token, [switch] $Force, [string] $FunctionName = $MyInvocation.MyCommand ) # tokek check $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName if (-not $User) { $User = $Token.UserprincipalName } $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)" Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Invoking REST DELETE to uri: $($restUri)" Write-PSFMessage -Tag "RestData" -Level Debug -Message "REST body data: $($Body)" Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore $invokeParam = @{ Method = "DELETE" Uri = $restUri Body = $Body Headers = @{ "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )" "Content-Type" = $ContentType } } if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess($restUri, "Invoke DELETE") } if ($doAction) { try { $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing } catch { Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName } Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue } if ($data) { $data | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force $data } } function Invoke-MgaRestMethodGet { <# .SYNOPSIS Performs a rest GET against the graph API .DESCRIPTION Performs a rest GET against the graph API. Primarily used for internal commands. .PARAMETER Field The api child item under the username in the url of the api call. If this didn't make sense to you, you probably shouldn't be using this command ;) .PARAMETER User The user to execute this under. Defaults to the user the token belongs to. .PARAMETER Delta Indicates that the query is intend to be a delta query, so a delta-link property is added to the output-object ('@odata.deltaLink'). .PARAMETER DeltaLink Specifies the uri to query for delta objects on a query. .PARAMETER UserUnspecific Specfies that no user name or "me" should be added in uri for api call. This is used for calling "all company data" like "available teams" or such things. .PARAMETER ResultSize The user to execute this under. Defaults to the user the token belongs to. .PARAMETER ApiConnection The URI for the Microsoft Graph connection .PARAMETER ApiVersion The version used for queries in Microsoft Graph connection .PARAMETER Token The access token to use to connect. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> Invoke-MgaRestMethodGet -Field 'mailFolders' -Token $Token -User $User Retrieves a list of email folders for the user $User, using the token stored in $Token #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(DefaultParameterSetName = 'Default')] [Alias('Invoke-MgaGetMethod')] param ( [ValidateNotNullOrEmpty()] [string] $Field, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'DeltaLink')] [string] $User, [Parameter(ParameterSetName = 'Default')] [switch] $Delta, [Parameter(ParameterSetName = 'DeltaLink')] [string] $DeltaLink, [Parameter(ParameterSetName = 'UserUnspecific')] [Alias('NoUserName', "NoUser", "NoUserSpecific")] [switch] $UserUnspecific, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [String] $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'), [string] $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'), [MSGraph.Core.AzureAccessToken] $Token, [string] $FunctionName = $MyInvocation.MyCommand ) # tokek check $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName #region variable definition if ($PSCmdlet.ParameterSetName -like "DeltaLink") { Write-PSFMessage -Level VeryVerbose -Message "ParameterSet $($PSCmdlet.ParameterSetName) - constructing delta query" -Tag "ParameterSetHandling" $restUri = $DeltaLink $Delta = $true $User = ([uri]$restUri).AbsolutePath.split('/')[2] } elseif ($PSCmdlet.ParameterSetName -like "UserUnspecific") { $restUri = "$($ApiConnection)/$($ApiVersion)/$($Field)" } else { if (-not $User) { $User = $Token.UserprincipalName } $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)" if ($Delta) { $restUri = $restUri + "/delta" } } if ($ResultSize -eq 0) { $ResultSize = [Int64]::MaxValue } [Int64]$i = 0 [Int64]$overResult = 0 $tooManyItems = $false $output = @() #endregion variable definition #region query data do { Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Get REST data: $($restUri)" Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore $invokeParam = @{ Method = "Get" Uri = $restUri Headers = @{ "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )" "Content-Type" = "application/json" } } try { $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing } catch { Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName } Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue if ("Value" -in $data.psobject.Properties.Name) { # Multi object with value property returned by api call [array]$value = $data.Value Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Retrieving $($value.Count) records from query" $i = $i + $value.Count if ($i -lt $ResultSize) { $restUri = $data.'@odata.nextLink' } else { $restUri = "" $tooManyItems = $true $overResult = $ResultSize - ($i - $value.Count) Write-PSFMessage -Tag "ResultSize" -Level Verbose -Message "Resultsize ($ResultSize) exeeded. Output $($overResult) object(s) in record set." } } else { # Single object without value property returned by api call Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Single item retrived. Outputting data." [array]$value = $data $restUri = "" } if ((-not $tooManyItems) -or ($overResult -gt 0)) { # check if resultsize is reached if ($overResult -gt 0) { $output = $output + $Value[0..($overResult - 1)] } else { $output = $output + $Value } } } while ($restUri) #endregion query data #region output data $output | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force if ($Delta) { if ('@odata.deltaLink' -in $data.psobject.Properties.Name) { $output | Add-Member -MemberType NoteProperty -Name '@odata.deltaLink' -Value $data.'@odata.deltaLink' -PassThru } else { $output | Add-Member -MemberType NoteProperty -Name '@odata.deltaLink' -Value $data.'@odata.nextLink' -PassThru } } else { $output } if ($tooManyItems) { # write information to console if resultsize exceeds if ($Delta) { Write-PSFMessage -Tag "GetData" -Level Host -Message "Reaching maximum ResultSize before finishing delta query. Next delta query will continue on pending objects. Current ResultSize: $($ResultSize)" -FunctionName $FunctionName } else { Write-PSFMessage -Tag "GetData" -Level Warning -Message "Too many items. Reaching maximum ResultSize before finishing query. You may want to increase the ResultSize. Current ResultSize: $($ResultSize)" -FunctionName $FunctionName } } #endregion output data } function Invoke-MgaRestMethodPatch { <# .SYNOPSIS Performs a REST PATCH against the graph API .DESCRIPTION Performs a REST PATCH against the graph API. Primarily used for internal commands. .PARAMETER Field The api child item under the username in the url of the api call. If this didn't make sense to you, you probably shouldn't be using this command ;) .PARAMETER User The user to execute this under. Defaults to the user the token belongs to. .PARAMETER Body JSON date as string to send as body on the REST call .PARAMETER ContentType Nature of the data in the body of an entity. Required. .PARAMETER ApiConnection The URI for the Microsoft Graph connection .PARAMETER ApiVersion The version used for queries in Microsoft Graph connection .PARAMETER Token The access token to use to connect. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> Invoke-MgaRestMethodPatch -Field "messages/$($id)" -Body '{ "isRead": true }' -Token $Token Set a message as readed. The token stored in $Token is used for the api call. #> [CmdletBinding(DefaultParameterSetName = 'Default')] [Alias('Invoke-MgaPatchMethod')] param ( [Parameter(Mandatory = $true)] [string] $Field, [string] $User, [String] $Body, [ValidateSet("application/json")] [String] $ContentType = "application/json", [String] $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'), [string] $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'), [MSGraph.Core.AzureAccessToken] $Token, [string] $FunctionName = $MyInvocation.MyCommand ) # tokek check $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName if (-not $User) { $User = $Token.UserprincipalName } $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)" Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Invoking REST PATCH to uri: $($restUri)" Write-PSFMessage -Tag "RestData" -Level Debug -Message "REST body data: $($Body)" Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore $invokeParam = @{ Method = "Patch" Uri = $restUri Body = $Body Headers = @{ "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )" "Content-Type" = $ContentType } } try { $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing } catch { Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName } Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue if ($data) { $data | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force $data } } function Invoke-MgaRestMethodPost { <# .SYNOPSIS Performs a REST POST against the graph API .DESCRIPTION Performs a REST POST against the graph API. Primarily used for internal commands. .PARAMETER Field The api child item under the username in the url of the api call. If this didn't make sense to you, you probably shouldn't be using this command ;) .PARAMETER User The user to execute this under. Defaults to the user the token belongs to. .PARAMETER Body JSON date as string to send as body on the REST call .PARAMETER ContentType Nature of the data in the body of an entity. Required. .PARAMETER ApiConnection The URI for the Microsoft Graph connection .PARAMETER ApiVersion The version used for queries in Microsoft Graph connection .PARAMETER Token The access token to use to connect. .PARAMETER FunctionName Name of the higher function which is calling this function. .EXAMPLE PS C:\> Invoke-MgaRestMethodPost -Field "messages/$($id)/reply" -Body '{"comment": "comment-value"}' -Token $Token Reply to the sender of a message with the id, stored in variable $id. The message is then saved in the Sent Items folder. The token stored in $Token is used for the api call. #> [CmdletBinding(DefaultParameterSetName = 'Default')] [Alias('Invoke-MgaPostMethod')] param ( [Parameter(Mandatory = $true)] [string] $Field, [string] $User, [String] $Body, [ValidateSet("application/json")] [String] $ContentType = "application/json", [String] $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'), [string] $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'), [MSGraph.Core.AzureAccessToken] $Token, [string] $FunctionName = $MyInvocation.MyCommand ) # tokek check $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName if (-not $User) { $User = $Token.UserprincipalName } $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)" Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Invoking REST POST to uri: $($restUri)" Write-PSFMessage -Tag "RestData" -Level Debug -Message "REST body data: $($Body)" Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore $invokeParam = @{ Method = "Post" Uri = $restUri Body = $Body Headers = @{ "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )" "Content-Type" = $ContentType } } try { $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing } catch { Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName } Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue if ($restError) { Stop-PSFFunction -Tag "RestData" -Message $parseError[0].Exception -Exception $parseError[0].Exception -EnableException $false -Category ConnectionError -FunctionName $FunctionName return } if ($data) { $data | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force $data } } function New-MgaAccessToken { <# .SYNOPSIS Creates an access token for contacting the specified application endpoint .DESCRIPTION Creates an access token for contacting the specified application endpoint .PARAMETER MailboxName The email address of the mailbox to access .PARAMETER Credential The credentials to use to authenticate the request. Using this avoids the need to visually interact with the logon screen. Only works for accounts that have once logged in visually, but can be used from any machine. .PARAMETER ClientId The ID of the client to connect with. This is the ID of the registered application. .PARAMETER RedirectUrl Some weird vodoo. Leave it as it is, unless you know better .PARAMETER ShowLoginWindow Force to show login window with account selection again. .PARAMETER Register Registers the token, so all subsequent calls to Exchange Online reuse it by default. .PARAMETER PassThru Outputs the token to the console, even when the register switch is set .PARAMETER IdentityPlatformVersion Specifies the endpoint version of the logon platform (Microsoft identity platform) where to connect for logon. Use 2.0 if you want to login with a Microsoft Account. For more information goto https://docs.microsoft.com/en-us/azure/active-directory/develop/about-microsoft-identity-platform .PARAMETER Tenant The entry point to sign into. The allowed values are common, organizations, consumers. .PARAMETER Permission Only applies if IdentityPlatformVersion version 2.0 is used. Specify the requested permission in the token. .PARAMETER ResourceUri The App ID URI of the target web API (secured resource). It may be https://graph.microsoft.com .EXAMPLE PS C:\> New-MgaAccessToken -Register For best usage and convinience, mostly, this is what you want to use. Requires an interactive session with a user handling the web UI. For addition the aquired token will be registered in the module as default value to use with all the commands. .EXAMPLE PS C:\> $token = New-MgaAccessToken Requires an interactive session with a user handling the web UI. .EXAMPLE PS C:\> $token = New-MgaAccessToken -Credential $cred Generates a token with the credentials specified in $cred. This is not supported for personal accounts (Micrsoft Accounts). .EXAMPLE PS C:\> New-MgaAccessToken -Register -ShowLoginWindow -ClientId '4a6acbac-d325-47a3-b59b-d2e9e05a37c1' -RedirectUrl 'urn:ietf:wg:oauth:2.0:oob' -IdentityPlatformVersion '2.0' Requires an interactive session with a user handling the web UI. Always prompt for account selection windows. Connecting against Azure Application with ID '4a6acbac-d325-47a3-b59b-d2e9e05a37c1'. Specifies RedirectUrl 'urn:ietf:wg:oauth:2.0:oob' (default value for interactive apps). Use Authentication Plattform 1.0, which only allows AzureAD business accounts to logon. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(DefaultParameterSetName = "LoginWithWebForm")] [Alias('Connect-MgaGraph')] param ( [Parameter(ParameterSetName = 'LoginWithCredentialObject')] [PSCredential] $Credential, [System.Guid] $ClientId = (Get-PSFConfigValue -FullName MSGraph.Tenant.Application.ClientID -NotNull), [string] $RedirectUrl = (Get-PSFConfigValue -FullName MSGraph.Tenant.Application.RedirectUrl -Fallback "urn:ietf:wg:oauth:2.0:oob"), [Parameter(ParameterSetName = 'LoginWithWebForm')] [Alias('Force')] [switch] $ShowLoginWindow, [ValidateSet('1.0', '2.0')] [string] $IdentityPlatformVersion = (Get-PSFConfigValue -FullName MSGraph.Tenant.Authentiation.IdentityPlatformVersion -Fallback '2.0'), [String[]] $Permission, [String] $ResourceUri = (Get-PSFConfigValue -FullName MSGraph.Tenant.ApiConnection -Fallback 'https://graph.microsoft.com'), [ValidateSet('common', 'organizations', 'consumers')] [String] $Tenant = 'common', [switch] $Register, [switch] $PassThru ) begin { $baselineTimestamp = [datetime]"1970-01-01Z00:00:00" $endpointBaseUri = (Get-PSFConfigValue -FullName MSGraph.Tenant.Authentiation.Endpoint -Fallback 'https://login.microsoftonline.com') if ($IdentityPlatformVersion -like '1.0' -and $Permission) { Write-PSFMessage -Level Warning -Message "Individual pemissions are not supported in combination with IdentityPlatformVersion 1.0. Specified Permission ($([String]::Join(", ", $Permission))) in parameter will be ignored" -Tag "ParameterSetHandling" $Permission = "" } } process { #region variable definitions switch ($IdentityPlatformVersion) { '1.0' { $endpointUri = "$($endpointBaseUri)/$($Tenant)/oauth2" } '2.0' { if ($Credential -and ($Tenant -notlike "organizations")) { $endpointUri = "$($endpointBaseUri)/organizations/oauth2/V2.0" } else { $endpointUri = "$($endpointBaseUri)/$($Tenant)/oauth2/V2.0" } } } $endpointUriAuthorize = "$($endpointUri)/authorize" $endpointUriToken = "$($endpointUri)/token" Write-PSFMessage -Level Verbose -Message "Start authentication against endpoint $($endpointUri). (Identity platform version $($IdentityPlatformVersion))" -Tag "Authorization" Write-PSFMessage -Level VeryVerbose -Message "Try to get token for usage of application ClientID: $($ClientId) to interact with ResourceAPI: $($ResourceUri)" -Tag "Authorization" if ($IdentityPlatformVersion -like '2.0') { [array]$scopes = "offline_access", "openid" # offline_access to get refreshtoken foreach ($permissionItem in $Permission) { $scopes = $scopes + "$($resourceUri)/$($permissionItem)" } $scope = [string]::Join(" ", $scopes) Remove-Variable -Name scopes -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false Write-PSFMessage -Level VeryVerbose -Message "Using scope: $($scope)" -Tag "Authorization" } #endregion variable definitions #region Request an authorization code (login procedure) if (-not $Credential) { # build authorization string with web form # Info https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#request-an-authorization-code Write-PSFMessage -Level Verbose -Message "Authentication is done by code. Query authentication from login form." -Tag "Authorization" $queryHash = [ordered]@{ client_id = "$($ClientId)" response_type = "code" redirect_uri = [System.Web.HttpUtility]::UrlEncode($redirectUrl) } switch ($IdentityPlatformVersion) { '1.0' { $queryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) # optional, but recommended if ($ShowLoginWindow) { $queryHash.Add("prompt", "select_account") } } '2.0' { $queryHash.Add("scope", [uri]::EscapeDataString($scope)) if ($ShowLoginWindow) { $queryHash.Add("prompt", "login") } } } # Show login windows (web form) [string]$url = $endpointUriAuthorize + (Convert-UriQueryFromHash $queryHash) $phase1auth = Show-OAuthWindow -Url $url if (-not $phase1auth.code) { $msg = "Authentication failed. Unable to obtain AccessToken.`n$($phase1auth.error_description)" if ($phase1auth.error) { $msg = $phase1auth.error.ToUpperInvariant() + " - " + $msg } Stop-PSFFunction -Message $msg -Tag "Authorization" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg)) } Remove-Variable -Name url -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false # build authorization string with authentication code from web form auth $tokenQueryHash = [ordered]@{ client_id = "$($ClientId)" grant_type = "authorization_code" code = "$($phase1auth.code)" redirect_uri = "$($redirectUrl)" } switch ($IdentityPlatformVersion) { '1.0' { $tokenQueryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) } '2.0' { $tokenQueryHash.Add("scope", [uri]::EscapeDataString($scope)) } } $authorizationPostRequest = Convert-UriQueryFromHash $tokenQueryHash -NoQuestionmark } else { # build authorization string with plain text credentials # Info https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow#request-an-access-token Write-PSFMessage -Level Verbose -Message "Authentication is done by specified credentials. (No TwoFactor-Authentication supported!)" -Tag "Authorization" $tokenQueryHash = [ordered]@{ grant_type = "password" username = $Credential.UserName password = $Credential.GetNetworkCredential().password client_id = $ClientId } switch ($IdentityPlatformVersion) { '1.0' { $tokenQueryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) } '2.0' { $tokenQueryHash.Add("scope", [uri]::EscapeDataString($scope)) } } $authorizationPostRequest = Convert-UriQueryFromHash $tokenQueryHash -NoQuestionmark } #endregion Request an authorization code (login procedure) #region Request an access token $content = New-Object -TypeName "System.Net.Http.StringContent" -ArgumentList ($authorizationPostRequest, [System.Text.Encoding]::UTF8, "application/x-www-form-urlencoded") $httpClient = New-HttpClient $clientResult = $httpClient.PostAsync([Uri]($endpointUriToken), $content) if ($clientResult.Result.StatusCode -eq [System.Net.HttpStatusCode]"OK") { Write-PSFMessage -Level Verbose -Message "AccessToken granted. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase)" -Tag "Authorization" } else { $httpClient.CancelPendingRequests() $msg = "Request for AccessToken failed. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase) `n$($jsonResponse.error_description)" Stop-PSFFunction -Message $msg -Tag "Authorization" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg)) } #endregion Request an access token #region Build output object $jsonResponse = ConvertFrom-Json -InputObject $clientResult.Result.Content.ReadAsStringAsync().Result -ErrorAction Ignore $resultObject = New-Object -TypeName MSGraph.Core.AzureAccessToken -Property @{ IdentityPlatformVersion = $IdentityPlatformVersion TokenType = $jsonResponse.token_type AccessToken = $null RefreshToken = $null IDToken = $null Credential = $Credential ClientId = $ClientId Resource = $resourceUri AppRedirectUrl = $RedirectUrl } switch ($IdentityPlatformVersion) { '1.0' { $resultObject.Scope = $jsonResponse.scope -split " " $resultObject.ValidUntilUtc = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToUniversalTime() $resultObject.ValidFromUtc = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToUniversalTime() $resultObject.ValidUntil = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().IsDaylightSavingTime() ) $resultObject.ValidFrom = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().IsDaylightSavingTime() ) } '2.0' { $resultObject.Scope = $jsonResponse.scope.Replace("$ResourceUri/", '') -split " " $resultObject.ValidUntilUtc = (Get-Date).AddSeconds($jsonResponse.expires_in).ToUniversalTime() $resultObject.ValidFromUtc = (Get-Date).ToUniversalTime() $resultObject.ValidUntil = (Get-Date).AddSeconds($jsonResponse.expires_in).ToLocalTime() $resultObject.ValidFrom = (Get-Date).ToLocalTime() } } # Insert token data into output object. done as secure string to prevent text output of tokens if ($jsonResponse.psobject.Properties.name -contains "refresh_token") { $resultObject.RefreshToken = ($jsonResponse.refresh_token | ConvertTo-SecureString -AsPlainText -Force) } if ($jsonResponse.psobject.Properties.name -contains "id_token") { $resultObject.IDToken = ($jsonResponse.id_token | ConvertTo-SecureString -AsPlainText -Force) $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.id_token } if ($jsonResponse.psobject.Properties.name -contains "access_token") { $resultObject.AccessToken = ($jsonResponse.access_token | ConvertTo-SecureString -AsPlainText -Force) if ($jsonResponse.access_token.Contains(".") -and $jsonResponse.access_token.StartsWith("eyJ")) { $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.access_token } } # Getting validity period out of AccessToken information if ($resultObject.AccessTokenInfo -and ($resultObject.AccessTokenInfo.TenantID.ToString() -notlike "9188040d-6c67-4c5b-b112-36a304b66dad") ) { $resultObject.ValidUntilUtc = $resultObject.AccessTokenInfo.ExpirationTime.ToUniversalTime() $resultObject.ValidFromUtc = $resultObject.AccessTokenInfo.NotBefore.ToUniversalTime() $resultObject.ValidUntil = $resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().IsDaylightSavingTime() ) $resultObject.ValidFrom = $resultObject.AccessTokenInfo.NotBefore.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.NotBefore.ToLocalTime().IsDaylightSavingTime() ) } #endregion Build output object #region Output the object # Checking if token is valid # ToDo implement "validating token information" -> https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens if ($resultObject.IsValid) { if ($Register) { $script:msgraph_Token = $resultObject if ($PassThru) { $resultObject } } else { $resultObject } } else { Stop-PSFFunction -Message "Token failure. Acquired token is not valid" -EnableException $true -Tag "Authorization" } #endregion Output the object } end {} } function Register-MgaAccessToken { <# .SYNOPSIS Registers an access token .DESCRIPTION Registers an access token, so all subsequent calls to Exchange Online reuse it by default. .PARAMETER Token The Token to register as default token for subsequent calls. .PARAMETER PassThru Outputs the token to the console .EXAMPLE PS C:\> Get-MgaRegisteredAccessToken Output the registered access token #> [CmdletBinding (SupportsShouldProcess = $false, ConfirmImpact = 'Medium')] [OutputType([MSGraph.Core.AzureAccessToken])] param ( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] [ValidateNotNullOrEmpty()] [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin {} process{ $script:msgraph_Token = $Token if ($PassThru) { $script:msgraph_Token } } end {} } function Update-MgaAccessToken { <# .SYNOPSIS Updates an existing access token .DESCRIPTION Updates an existing access token for contacting the specified application endpoint as long as the token is still valid. Otherwise, a new access is called through New-MgaAccessToken. .PARAMETER Token The token object to renew. .PARAMETER Register Registers the renewed token, so all subsequent calls to Exchange Online reuse it by default. .PARAMETER PassThru Outputs the token to the console, even when the register switch is set .EXAMPLE PS C:\> Update-MgaAccessToken -Register Updates the default (registered) Accesstoken and register it again as the default token .EXAMPLE PS C:\> $token = Update-MgaAccessToken -Token $token Updates the AccessToken in $token and output a new AccessToken object. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(DefaultParameterSetName = "Default")] param ( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [MSGraph.Core.AzureAccessToken] $Token, [Parameter(ParameterSetName = 'Register')] [switch] $Register, [Parameter(ParameterSetName = 'Register')] [switch] $PassThru ) begin { $endpointBaseUri = (Get-PSFConfigValue -FullName MSGraph.Tenant.Authentiation.Endpoint -Fallback 'https://login.microsoftonline.com') $baselineTimestamp = [datetime]"1970-01-01Z00:00:00" } process { $Token = Resolve-Token -Token $Token -FunctionName $MyInvocation.MyCommand $Credential = $Token.Credential $ClientId = $Token.ClientId $RedirectUrl = $Token.AppRedirectUrl.ToString() $ResourceUri = $Token.Resource.ToString().TrimEnd('/') $Permission = ($Token.Scope | Where-Object { $_ -notin "offline_access", "openid", "profile", "email" }) $IdentityPlatformVersion = $Token.IdentityPlatformVersion if (-not $Token.IsValid) { Write-PSFMessage -Level Warning -Message "Token lifetime already expired and can't be newed. New authentication is required. Calling New-MgaAccessToken..." -Tag "Authorization" $paramsNewToken = @{ PassThru = $True ClientId = $ClientId RedirectUrl = $RedirectUrl ResourceUri = $ResourceUri Permission = $Permission IdentityPlatformVersion = $IdentityPlatformVersion } if ($Credential) { $paramsNewToken.Add("Credential", $Credential ) } if ($Register -or ($script:msgraph_Token.AccessTokenInfo.Payload -eq $Token.AccessTokenInfo.Payload) ) { $paramsNewToken.Add("Register", $true) } if (Test-PSFParameterBinding -ParameterName Verbose) { $paramsNewToken.Add("Verbose", $true) } $resultObject = New-MgaAccessToken @paramsNewToken if ($PassThru) { return $resultObject } else { return } } Write-PSFMessage -Level Verbose -Message "Start token refresh for application $( if($Token.AppName){$Token.AppName}else{$ClientId} ). (Identity platform version $($IdentityPlatformVersion))" -Tag "Authorization" switch ($IdentityPlatformVersion) { '1.0' { $endpointUriToken = "$($endpointBaseUri)/common/oauth2/token" } '2.0' { if ($token.Credential) { $endpointUriToken = "$($endpointBaseUri)/organizations/oauth2/V2.0/token" } else { $endpointUriToken = "$($endpointBaseUri)/common/oauth2/V2.0/token" } [array]$scopes = "offline_access", "openid" foreach ($permissionItem in $Permission) { $scopes = $scopes + "$($resourceUri)/$($permissionItem)" } $scope = [string]::Join(" ", $scopes) Remove-Variable -Name scopes -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false Write-PSFMessage -Level VeryVerbose -Message "Using scope: $($scope)" -Tag "Authorization" } } $queryHash = [ordered]@{ grant_type = "refresh_token" client_id = $ClientId refresh_token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token.RefreshToken)) } switch ($IdentityPlatformVersion) { '1.0' { $queryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) } '2.0' { $queryHash.Add("scope", [uri]::EscapeDataString($scope)) $queryHash.Add("redirect_uri", [System.Web.HttpUtility]::UrlEncode($redirectUrl)) } } $authorizationPostRequest = Convert-UriQueryFromHash $queryHash -NoQuestionmark $content = New-Object System.Net.Http.StringContent($authorizationPostRequest, [System.Text.Encoding]::UTF8, "application/x-www-form-urlencoded") $httpClient = New-HttpClient $clientResult = $httpClient.PostAsync([Uri]$endpointUriToken, $content) $jsonResponse = ConvertFrom-Json -InputObject $clientResult.Result.Content.ReadAsStringAsync().Result -ErrorAction Ignore if ($clientResult.Result.StatusCode -eq [System.Net.HttpStatusCode]"OK") { Write-PSFMessage -Level Verbose -Message "AccessToken renewal successful. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase)" -Tag "Authorization" } else { $httpClient.CancelPendingRequests() $msg = "Request for AccessToken failed. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase) `n$($jsonResponse.error_description)" Stop-PSFFunction -Message $msg -Tag "Authorization" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg)) } # Build output object $resultObject = New-Object -TypeName MSGraph.Core.AzureAccessToken -Property @{ IdentityPlatformVersion = $IdentityPlatformVersion TokenType = $jsonResponse.token_type AccessToken = $null RefreshToken = $null IDToken = $null Credential = $Credential ClientId = $ClientId Resource = $resourceUri AppRedirectUrl = $RedirectUrl } switch ($IdentityPlatformVersion) { '1.0' { $resultObject.Scope = $jsonResponse.scope -split " " $resultObject.ValidUntilUtc = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToUniversalTime() $resultObject.ValidFromUtc = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToUniversalTime() $resultObject.ValidUntil = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().IsDaylightSavingTime() ) $resultObject.ValidFrom = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().IsDaylightSavingTime() ) } '2.0' { $resultObject.Scope = $jsonResponse.scope.Replace("$ResourceUri/", '') -split " " $resultObject.ValidUntilUtc = (Get-Date).AddSeconds($jsonResponse.expires_in).ToUniversalTime() $resultObject.ValidFromUtc = (Get-Date).ToUniversalTime() $resultObject.ValidUntil = (Get-Date).AddSeconds($jsonResponse.expires_in).ToLocalTime() $resultObject.ValidFrom = (Get-Date).ToLocalTime() } } # Insert token data into output object. done as secure string to prevent text output of tokens if ($jsonResponse.psobject.Properties.name -contains "refresh_token") { $resultObject.RefreshToken = ($jsonResponse.refresh_token | ConvertTo-SecureString -AsPlainText -Force) } if ($jsonResponse.psobject.Properties.name -contains "id_token") { $resultObject.IDToken = ($jsonResponse.id_token | ConvertTo-SecureString -AsPlainText -Force) $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.id_token } if ($jsonResponse.psobject.Properties.name -contains "access_token") { $resultObject.AccessToken = ($jsonResponse.access_token | ConvertTo-SecureString -AsPlainText -Force) if ($jsonResponse.access_token.Contains(".") -and $jsonResponse.access_token.StartsWith("eyJ")) { $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.access_token } } # Getting validity period out of AccessToken information if ($resultObject.AccessTokenInfo -and $resultObject.AccessTokenInfo.TenantID.ToString() -notlike "9188040d-6c67-4c5b-b112-36a304b66dad") { $resultObject.ValidUntilUtc = $resultObject.AccessTokenInfo.ExpirationTime.ToUniversalTime() $resultObject.ValidFromUtc = $resultObject.AccessTokenInfo.NotBefore.ToUniversalTime() $resultObject.ValidUntil = $resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().IsDaylightSavingTime() ) $resultObject.ValidFrom = $resultObject.AccessTokenInfo.NotBefore.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.NotBefore.ToLocalTime().IsDaylightSavingTime() ) } # Checking if token is valid # ToDo implement "validating token information" -> https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens if ($resultObject.IsValid) { if ($Register) { $script:msgraph_Token = $resultObject if ($PassThru) { $resultObject } } else { $resultObject } } else { Stop-PSFFunction -Message "Token failure. Acquired token is not valid" -EnableException $true -Tag "Authorization" } } end { } } function Add-MgaMailAttachment { <# .SYNOPSIS Add attachment(s) to a draft message in Exchange Online using the graph api. .DESCRIPTION Add attachment(s) to a draft message in Exchange Online using the graph api. Currently, only file attachments are supportet. .PARAMETER Message Carrier object for Pipeline input. This can be the id of the message or a message object passed in. .PARAMETER File The path to the file to add as attachment. .PARAMETER Link The ReferenceAttachment (aka "modern attachment", aka OneDriveLink) to add to the message.. .PARAMETER Item The Outlook item to add as attachment. .PARAMETER Force Enforce adding attachment, even when the message is not in draft mode. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/message-post-attachments?view=graph-rest-1.0 .EXAMPLE PS C:\> $mail | Add-MgaMailAttachment -Path "logfile.txt" Add "logfile.txt" as attachment to message(s) in the variable $mail, The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Drafts -ResultSize 1 .EXAMPLE PS C:\> $mail | Add-MgaMailAttachment -Link $ReferenceAttachment Add a modern attachment as attachment (reference link) to message(s) in the variable $mail, The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Drafts -ResultSize 1 The variable $ReferenceAttachment has to be a object [MSGraph.Exchange.Attachment.ReferenceAttachment] #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'FileAttachment')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('InputObject', 'Id', 'Mail', 'MailMessage', 'MessageId', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Mandatory = $true, ParameterSetName = 'FileAttachment')] [Alias('Path', 'FileName', 'FilePath')] [string[]] $File, [Parameter(Mandatory = $true, ParameterSetName = 'ReferenceAttachment')] [Alias('ReferenceAttachment', 'LinkPath', 'Uri', 'Url')] [MSGraph.Exchange.Attachment.ReferenceAttachment[]] $Link, [Parameter(Mandatory = $true, ParameterSetName = 'ItemAttachment')] [Alias('Event', 'OutlookItem')] [psobject[]] $Item, [switch] $Force, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand switch ($PSCmdlet.ParameterSetName) { 'FileAttachment' { $filesToAttach = @() foreach ($filePath in $File) { try { $fileItem = Get-ChildItem -Path $filePath -File -ErrorAction Stop $fileItem | Add-Member -MemberType NoteProperty -Name contentBytes -Value ( [System.Convert]::ToBase64String( [System.IO.File]::ReadAllBytes($fileItem.FullName) ) ) $filesToAttach = $filesToAttach + $fileItem } catch { Stop-PSFFunction -Message "Specified path '$($filePath)' is invalid or not a file. Please specify a valid file." -EnableException $true -Exception $errorvariable.Exception -Category InvalidData -Tag "Attachment" } } $namesFileToAttach = "'$([string]::Join("', '",$filesToAttach.Name))'" } 'ReferenceAttachment' { # ToDo implemented convinient parsing for referenceAttachments $namesFileToAttach = "'$([string]::Join("', '",$Link.Name))'" } 'ItemAttachment' { # ToDo implemented adding item attachment Stop-PSFFunction -Message "adding item attachment is not implemented, yet." foreach ($itemObject in $Item) { } } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } } process { foreach ($messageItem in $Message) { Write-PSFMessage -Level Debug -Message "Adding attachment(s) $($namesFileToAttach) to message '$($messageItem)' by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } if (-not $messageItem.InputObject.IsDraft -and (-not $Force)) { if ($PSCmdlet.ShouldContinue("The mesaage is not a draft message! Would you really like to add attachment(s) $($namesFileToAttach) to message '$($messageItem)'?", "$($messageItem) is not a draft message") ) { Write-PSFMessage -Level Verbose -Message "Confirmation specified to add attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce" } else { Write-PSFMessage -Level Important -Message "Abort adding attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce" return } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required # prepare parameters for rest call $invokeParam = @{ "Field" = "messages/$($messageItem.Id)/attachments" "Token" = $Token "User" = $User "ApiVersion" = "beta" "FunctionName" = $MyInvocation.MyCommand } $data = @() switch ($PSCmdlet.ParameterSetName) { 'FileAttachment' { foreach ($fileToAttach in $filesToAttach) { # prepare REST Body $bodyJSON = New-JsonAttachmentObject -Name $fileToAttach.Name -Size $fileToAttach.Length -IsInline $false -contentBytes $fileToAttach.contentBytes -FunctionName $MyInvocation.MyCommand $invokeParam.Add("Body", $bodyJSON) # add attachment if ($pscmdlet.ShouldProcess("Message '$($messageItem)'", "Add FileAttachment '$($fileToAttach.FullName)'")) { Write-PSFMessage -Level Verbose -Message "Add '$($fileToAttach.FullName)' to message '$($messageItem)'" -Tag "AddData" $data = $data + (Invoke-MgaRestMethodPost @invokeParam) } $invokeParam.Remove("Body") } } 'ReferenceAttachment' { foreach ($linkItem in $Link) { # prepare REST Body $bodyJSON = New-JsonAttachmentObject -SourceUrl $linkItem.SourceUrl -Name $linkItem.Name -ProviderType $linkItem.ProviderType -IsFolder $linkItem.IsFolder -Permission $linkItem.Permission -FunctionName $MyInvocation.MyCommand $invokeParam.Add("Body", $bodyJSON) # add attachment if ($pscmdlet.ShouldProcess("Message '$($messageItem)'", "Add ReferenceAttachment '$($linkItem.Name)'")) { Write-PSFMessage -Level Verbose -Message "Getting '$($linkItem.ToString())' as ReferenceAttachment to message '$($messageItem)'" -Tag "AddData" $data = $data + (Invoke-MgaRestMethodPost @invokeParam) } $invokeParam.Remove("Body") } } 'ItemAttachment' { # ToDo implemented adding item attachment foreach ($itemObject in $Item) { } } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } #region output data foreach ($output in $data) { if ($PassThru) { $AttachmentObject = New-MgaAttachmentObject -RestData $output -ParentObject $messageItem.InputObject -ApiVersion "beta" -ResultSize $ResultSize -User $User -Token $Token -FunctionName $MyInvocation.MyCommand $AttachmentObject } } #endregion output data } } end { } } function Export-MgaMailAttachment { <# .SYNOPSIS Export a mail attachment to a file .DESCRIPTION Export/saves a mail attachment to a file .PARAMETER Attachment The attachment object to export .PARAMETER Path The directory where to export the attachment .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Export-MgaMailAttachment -Attachment $attachment -Path "$HOME" Export the attement to the users profile base directory #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [Alias('Save-MgaMailAttachment')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] [Alias('InputObject', 'AttachmentId', 'Id')] [MSGraph.Exchange.Attachment.AttachmentParameter[]] $Attachment, [String] $Path = (Get-Location).Path, [switch] $PassThru ) begin { if (Test-Path -Path $Path -IsValid) { if (-not (Test-Path -Path $Path -PathType Container)) { Stop-PSFFunction -Message "Specified path is a file and not a path. Please specify a directory." -EnableException $true -Category "InvalidPath" -Tag "Attachment" } } else { Stop-PSFFunction -Message "Specified path is not valid. Please specify a valid directory." -EnableException $true -Category "InvalidPath" -Tag "Attachment" } $Path = Resolve-Path -Path $Path } process { foreach ($attachmentItem in $Attachment) { #switching between different types to export switch ($attachmentItem.TypeName) { "MSGraph.Exchange.Attachment.FileAttachment" { if ($pscmdlet.ShouldProcess($attachmentItem, "Export to $($Path.Path)")) { Write-PSFMessage -Level Verbose -Message "Exporting attachment '$($attachmentItem)' to '$($Path.ToString())'" -Tag "ExportData" $attachmentItem.InputObject.ContentBytes | Set-Content -Path (Join-Path -Path $Path -ChildPath $attachmentItem.Name) -Encoding Byte } } "MSGraph.Exchange.Attachment.ItemAttachment" { if ($pscmdlet.ShouldProcess($attachmentItem, "Export to $($Path.Path)")) { Write-PSFMessage -Level Important -Message "Export of $($attachmentItem.TypeName) is not implemented, yet. Could not export '$($attachmentItem.InputObject)'" -Tag "ExportData" } } "MSGraph.Exchange.Attachment.ReferenceAttachment" { if ($pscmdlet.ShouldProcess($attachmentItem, "Export to $($Path.Path)")) { $shell = New-Object -ComObject ("WScript.Shell") $shortCut = $shell.CreateShortcut("$($Path.Path)\$($attachmentItem.InputObject.Name).lnk") $shortCut.TargetPath = $attachmentItem.InputObject.SourceUrl $shortCut.Save() } } "MSGraph.Exchange.Attachment.Attachment" { Write-PSFMessage -Level Warning -Message "$($attachmentItem) is not a exportable attachment." -Tag "ParameterSetHandling" } Default { Write-PSFMessage -Level Warning -Message "$($attachmentItem) is not a exportable attachment." -Tag "ParameterSetHandling" continue } } if ($PassThru) { $attachmentItem } } } end { } } function Get-MgaMailAttachment { <# .SYNOPSIS Retrieves the attachment object from a email message in Exchange Online using the graph api. .DESCRIPTION Retrieves the attachment object from a email message in Exchange Online using the graph api. .PARAMETER Message Carrier object for Pipeline input. This can be the id of the message or a message object passed in. .PARAMETER Name The name to filter by. (Client Side filtering) .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER IncludeInlineAttachment This will retrieve also attachments like pictures in the html body of the mail. .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaMailMessage | Get-MgaMailAttachment Return all emails attachments from all mails in the inbox of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaMailMessage | Get-MgaMailAttachment -Name "MyName*" Return all emails attachments with name MyName* from all mails in the inbox of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaMailMessage | Get-MgaMailAttachment -IncludeInlineAttachment Return also "inline" attachments, like pictures in html mails from all emails in the inbox of the user connected to through a token. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('InputObject', 'Id', 'Mail', 'MailMessage', 'MessageId', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Position = 1)] [Alias('Filter', 'NameFilter')] [string] $Name = "*", [switch] $IncludeInlineAttachment, [string] $User, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Mail.Read" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required #region query data $invokeParam = @{ "Field" = "messages/$($messageItem.Id)/attachments" "Token" = $Token "User" = $User "ResultSize" = $ResultSize "ApiVersion" = "beta" "FunctionName" = $MyInvocation.MyCommand } Write-PSFMessage -Level Verbose -Message "Getting attachment from message '$($messageItem)'" -Tag "QueryData" $data = Invoke-MgaRestMethodGet @invokeParam | Where-Object { $_.name -like $Name } if (-not $IncludeInlineAttachment) { $data = $data | Where-Object isInline -eq $false } #endregion query data #region output data foreach ($output in $data) { $AttachmentObject = New-MgaAttachmentObject -RestData $output -ParentObject $messageItem.InputObject -ApiVersion "beta" -ResultSize $ResultSize -User $User -Token $Token -FunctionName $MyInvocation.MyCommand $AttachmentObject } #endregion output data } } end { } } function Remove-MgaMailAttachment { <# .SYNOPSIS Remove attachment(s) from a email message(s) in Exchange Online using the graph api. .DESCRIPTION Remove attachment(s) from a email message(s) in Exchange Online using the graph api. .PARAMETER Message Carrier object for Pipeline input. This can be the id of the message or a message object passed in. .PARAMETER Attachment Carrier object for Pipeline input. This can be the id of the attachment or a attachment object passed in. .PARAMETER Name The name of the attachment to delete. .PARAMETER IncludeInlineAttachment Also search and remove InlineAttachment. Per default, only attachments outside a html body are recognized. .PARAMETER Force Suppress any confirmation request and enforce removing attachment on any kind of message. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the object to the console. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-1.0 .EXAMPLE PS C:\> Get-MgaMailMessage -Folder Drafts | Remove-MgaMailAttachment Delete attachment(s) from all mails in drafts folder of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaMailMessage -Folder Drafts | Remove-MgaMailAttachment -Name "MyName*" Delete attachment(s) with name MyName* from all mails in drafts folder of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaMailMessage -Folder Drafts | Remove-MgaMailAttachment -IncludeInlineAttachment Delete also "inline" attachments, like pictures in html mails from all emails in drafts folder of the user connected to through a token. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'MessageInputObject')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'MessageInput', Position = 0)] [Alias('Mail', 'MailMessage', 'MessageId', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'AttachmentInput', Position = 0)] [Alias('Attachments', 'AttachmentId', 'AttachmentObject')] [MSGraph.Exchange.Attachment.AttachmentParameter[]] $Attachment, [Parameter(ParameterSetName = 'MessageInput', Position = 1)] [Alias('Filter', 'NameFilter')] [string] $Name = "*", [switch] $IncludeInlineAttachment, [switch] $Force, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { if ($PSCmdlet.ParameterSetName -like 'MessageInput') { $Attachment = @() foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } if (-not $messageItem.InputObject.IsDraft -and (-not $Force)) { if ($PSCmdlet.ShouldContinue("The mesaage is not a draft message! Would you really like to add attachment(s) $($namesFileToAttach) to message '$($messageItem)'?", "$($messageItem) is not a draft message") ) { Write-PSFMessage -Level Verbose -Message "Confirmation specified to add attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce" } else { Write-PSFMessage -Level Important -Message "Abort adding attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce" return } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required $getAttachmentParam = @{ "Message" = $messageItem "Name" = $Name "User" = $User "Token" = $Token } if ($IncludeInlineAttachment) { $getAttachmentParam.Add("IncludeInlineAttachment", $true) } $output = (Get-MgaMailAttachment @getAttachmentParam | Where-Object { $_.name -like $Name }) if ($output) { foreach ($outputItem in $output) { $Attachment = $Attachment + [MSGraph.Exchange.Attachment.AttachmentParameter]$outputItem } } } if (-not $Attachment) { Write-PSFMessage -Level Important -Message "Nothing found to delete." -Tag "QueryData" } } foreach ($attachmentItem in $Attachment) { Write-PSFMessage -Level Debug -Message "Deleting attachment '$($attachmentItem)' from message '$($attachmentItem.InputObject.ParentObject.Name)' by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" # prepare parameters for rest call $invokeParam = @{ "Field" = "messages/$($attachmentItem.InputObject.ParentObject.Id)/attachments/$($attachmentItem.id)" "Token" = $Token "User" = $User "Confirm" = $false "FunctionName" = $MyInvocation.MyCommand } # remove attachment if ($Force) { $proceed = $true } else { $proceed = $pscmdlet.ShouldProcess("Message '$($attachmentItem.InputObject.ParentObject.Name)'", "Delete attachment '$($attachmentItem)'") } if ($proceed) { Write-PSFMessage -Level Verbose -Message "Delete attachment '$($attachmentItem)' from message '$($attachmentItem.InputObject.ParentObject.Name)'" -Tag "RemoveData" Invoke-MgaRestMethodDelete @invokeParam } #region passthru data if ($PassThru) { $attachmentItem.InputObject } #endregion passthru data } } end { } } function Get-MgaExchCategory { <# .SYNOPSIS Retrieves categories in Exchange Online using the graph api. .DESCRIPTION Retrieves categories in Exchange Online using the graph api. .PARAMETER InputObject Carrier object for Pipeline input.Accepts CategoryObjects and strings. .PARAMETER Id The Id to filter by. (Client Side filtering) .PARAMETER Name The name to filter by. (Client Side filtering) .PARAMETER Color The color to filter by. (Client Side filtering) Tab completion is available on this parameter for the list of the 25 predefined colors. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaExchCategory Return all categories of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaExchCategory -Id "89101089-690d-4263-9470-b674e709a996" Return the category with the specified Id of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaExchCategory -Name "*category" Return all categories with names like "*category" of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaExchCategory -Color "Blue" Return all categories with names like "*category" of the user connected to through a token. #> [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.Exchange.Category.OutlookCategory])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'ByInputOBject')] [Alias('Category')] [MSGraph.Exchange.Category.CategoryParameter[]] $InputObject, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ById')] [Alias('IdFilter', 'FilterId')] [guid[]] $Id, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByName')] [Alias('NameFilter', 'FilterName')] [string[]] $Name, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByColor')] [Alias('ColorFilter', 'FilterColor')] [MSGraph.Exchange.Category.ColorName[]] $Color, [string] $User, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "MailboxSettings.Read" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { #region query data $invokeParam = @{ "Token" = $Token "User" = $User "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } $data = @() if ($PSCmdlet.ParameterSetName -like 'ByInputOBject') { foreach ($categoryItem in $InputObject) { #region checking input object type and query message if required if ($categoryItem.TypeName -like "System.String") { $categoryItem = Resolve-MailObjectFromString -Object $categoryItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $categoryItem) { continue } } $User = Resolve-UserInMailObject -Object $categoryItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required $invokeParam.Add("Field", "outlook/masterCategories/$($categoryItem.Id)") Write-PSFMessage -Level Verbose -Message "Get refresh on category '$($categoryItem)'" -Tag "QueryData" $data = $data + (Invoke-MgaRestMethodGet @invokeParam) $invokeParam.Remove("Field") } } else { $invokeParam.Add("Field", "outlook/masterCategories") Write-PSFMessage -Level Verbose -Message "Getting available categories" -Tag "QueryData" $data = $data + (Invoke-MgaRestMethodGet @invokeParam) } #endregion query data #region filter data switch ($PSCmdlet.ParameterSetName) { 'ById' { $data = foreach ($filter in $Id) { Write-PSFMessage -Level VeryVerbose -Message "Filtering on id '$($filter)'." -Tag "FilterData" $data | Where-Object Id -like $filter.Guid } } 'ByName' { $data = foreach ($filter in $Name) { Write-PSFMessage -Level VeryVerbose -Message "Filtering on name '$($filter)'." -Tag "FilterData" $data | Where-Object displayname -like $filter } } 'ByColor' { $data = foreach ($filter in $Color) { Write-PSFMessage -Level VeryVerbose -Message "Filtering on color '$($filter)'." -Tag "FilterData" $data | Where-Object Color -like ([MSGraph.Exchange.Category.OutlookCategory]::Parse($filter)) } } Default {} } #endregion filter data #region output data Write-PSFMessage -Level VeryVerbose -Message "Output $( ($data | Measure-Object).Count ) objects." -Tag "OutputData" foreach ($output in $data) { if ($output.User) { $User = $output.User } $categoryObject = [MSGraph.Exchange.Category.OutlookCategory]::new( $output.id, $output.displayName, $output.color, $User, $output) Write-PSFMessage -Level Debug -Message "Output new object '$($categoryObject)'." -Tag "OutputData" $categoryObject } #endregion output data } end { } } function New-MgaExchCategory { <# .SYNOPSIS Creates a new category in Exchange Online using the graph api. .DESCRIPTION Creates a new category in Exchange Online using the graph api. .PARAMETER Name The category name. .PARAMETER Color The color for the category. Tab completion is available on this parameter for the list of the 25 predefined colors. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/outlookuser-post-mastercategories?view=graph-rest-1.0 .EXAMPLE PS C:\> New-MgaExchCategory -Name "Important stuff" Creates a category "Important stuff" in the mailbox of the user connected to through a token. The new category will creates without color mapping. .EXAMPLE PS C:\> Get-MgaExchCategory -Name "Important stuff" -Color "Blue" Creates a blue colored category "Important stuff" in the mailbox of the user connected to through a token. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([MSGraph.Exchange.Category.OutlookCategory])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('DisplayName', 'Category', 'InputObject')] [string[]] $Name, [Parameter(Mandatory = $false, Position = 1)] [Alias('ColorName')] [MSGraph.Exchange.Category.ColorName] $Color, [string] $User, [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "MailboxSettings.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand if ($Color) { [String]$colorValue = [MSGraph.Exchange.Category.OutlookCategory]::Parse($Color) } else { [String]$colorValue = [MSGraph.Exchange.Category.ColorKey]::None } } process { foreach ($categoryName in $Name) { Write-PSFMessage -Level Verbose -Message "Create new category '$($categoryName)'" -Tag "CreateData" #region prepare rest call to create data $bodyJSON = @{ displayName = $categoryName color = $colorValue } | ConvertTo-Json $invokeParam = @{ "Field" = "outlook/masterCategories" "Body" = $bodyJSON "Token" = $Token "User" = $User "FunctionName" = $MyInvocation.MyCommand } #endregion prepare rest call to create data # create data if ($pscmdlet.ShouldProcess($categoryName, "Create")) { $data = Invoke-MgaRestMethodPost @invokeParam } #region output data foreach ($output in $data) { if ($output.User) { $User = $output.User } $categoryObject = [MSGraph.Exchange.Category.OutlookCategory]::new( $output.id, $output.displayName, $output.color, $User, $output) $categoryObject } #endregion output data } } end { } } function Remove-MgaExchCategory { <# .SYNOPSIS Remove a category in Exchange Online using the graph api. .DESCRIPTION Remove a category in Exchange Online using the graph api. .PARAMETER InputObject Carrier object for Pipeline input.Accepts CategoryObjects and strings. .PARAMETER Force Suppress any confirmation request and enforce removing. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the modified category to the console. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/outlookcategory-delete?view=graph-rest-1.0 .EXAMPLE PS C:\> Remove-MgaExchCategory -Name "Important stuff" Remove existing category "Important stuff" in the mailbox of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaExchCategory -Name "Important stuff" | Remove-MgaExchCategory -Force Remove existing category "Important stuff" WITHOUT CONFIRMATION in the mailbox of the user connected to through a token. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([MSGraph.Exchange.Category.OutlookCategory])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('Name', 'DisplayName', 'Category')] [MSGraph.Exchange.Category.CategoryParameter[]] $InputObject, [switch] $Force, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "MailboxSettings.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { foreach ($categoryItem in $InputObject) { Write-PSFMessage -Level Verbose -Message "Working on removal of category '$($categoryItem)'" -Tag "QueryData" #region checking input object type and query message if required if ($categoryItem.TypeName -like "System.String") { $categoryItem = Resolve-MailObjectFromString -Object $categoryItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $categoryItem) { continue } } $User = Resolve-UserInMailObject -Object $categoryItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required #region prepare rest call to create data $invokeParam = @{ "Field" = "outlook/masterCategories/$($categoryItem.Id)" "Token" = $Token "User" = $User "Confirm" = $false "FunctionName" = $MyInvocation.MyCommand } #endregion prepare rest call to create data # set data if ($Force) { $proceed = $true } else { $proceed = $pscmdlet.ShouldProcess($categoryItem.Name, "Delete") } if ($proceed) { Write-PSFMessage -Level Verbose -Message "Delete category '$($categoryItem)'." -Tag "RemoveData" Invoke-MgaRestMethodDelete @invokeParam } #region output data if ($PassThru) { $categoryItem.InputObject } #endregion output data } } end { } } function Set-MgaExchCategory { <# .SYNOPSIS Set a category in Exchange Online using the graph api. .DESCRIPTION Set a category in Exchange Online using the graph api. .PARAMETER InputObject Carrier object for Pipeline input.Accepts CategoryObjects and strings. .PARAMETER Color The color for the category. Tab completion is available on this parameter for the list of the 25 predefined colors. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the modified category to the console. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/outlookcategory-update?view=graph-rest-1.0 .EXAMPLE PS C:\> Set-MgaExchCategory -Name "Important stuff" -Color Black Set color "black" on existing category "Important stuff" in the mailbox of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaExchCategory -Name "Important stuff" | Set-MgaExchCategory -Color "Blue" Set color "blue" on existing category "Important stuff" in the mailbox of the user connected to through a token. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([MSGraph.Exchange.Category.OutlookCategory])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('Name', 'DisplayName', 'Category')] [MSGraph.Exchange.Category.CategoryParameter[]] $InputObject, <# Currently not available as writeable property on microsoft graph version 1.0 and beta [Parameter(Mandatory = $false)] [string] $NewName, #> [Parameter(Mandatory = $false)] [Alias('ColorName')] [MSGraph.Exchange.Category.ColorName] $Color, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "MailboxSettings.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { foreach ($categoryItem in $InputObject) { #region checking input object type and query message if required if ($categoryItem.TypeName -like "System.String") { $categoryItem = Resolve-MailObjectFromString -Object $categoryItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $categoryItem) { continue } } $User = Resolve-UserInMailObject -Object $categoryItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required #region prepare rest call to create data $bodyJSON = @{} $boundParameters = @() if ($NewName) { $boundParameters = $boundParameters + "NewName" $bodyJSON.Add("displayName", $NewName) } if ($Color) { $boundParameters = $boundParameters + "Color" [String]$colorValue = [MSGraph.Exchange.Category.OutlookCategory]::Parse($Color) $bodyJSON.Add("color", $colorValue.ToLower()) } $bodyJSON = $bodyJSON | ConvertTo-Json $invokeParam = @{ "Field" = "outlook/masterCategories/$($categoryItem.Id)" "Body" = $bodyJSON "Token" = $Token "User" = $User "FunctionName" = $MyInvocation.MyCommand } #endregion prepare rest call to create data Write-PSFMessage -Level Verbose -Message "Set property '$([string]::Join("', '", $boundParameters))' on category '$($categoryItem)'" -Tag "SetData" # set data if ($pscmdlet.ShouldProcess($categoryItem, "Set property '$([string]::Join("', '", $boundParameters))'")) { $data = Invoke-MgaRestMethodPatch @invokeParam } #region output data if ($PassThru) { foreach ($output in $data) { if ($output.User) { $User = $output.User } $categoryObject = [MSGraph.Exchange.Category.OutlookCategory]::new( $output.id, $output.displayName, $output.color, $User, $output) $categoryObject } } #endregion output data } } end { } } function Get-MgaMailFolder { <# .SYNOPSIS Get mail folder(s) in Exchange Online .DESCRIPTION Get mail folder(s) with metadata from Exchange Online via Microsoft Graph API .PARAMETER Name The name of the folder(S) to query. .PARAMETER IncludeChildFolders Output all subfolders on queried folder(s). .PARAMETER Recurse Iterates through the whole folder structure and query all subfolders. .PARAMETER Filter The name to filter by. (Client Side filtering) Try to avoid, when filtering on single name, use parameter -Name instead of -Filter. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaMailFolder Returns all folders in the mailbox of the connected user. .EXAMPLE PS C:\> Get-MgaMailFolder -Name Inbox Returns the "wellknown" inbox folder in the mailbox of the connected user. The wellknown folders can be specified by tab completion. .EXAMPLE PS C:\> Get-MgaMailFolder -Name Inbox -IncludeChildFolders Returns inbox and the next level of subfolders in the inbox of the connected user. .EXAMPLE PS C:\> Get-MgaMailFolder -Name Inbox -Recurse Returns inbox and the all subfolders underneath the inbox of the connected user. This one is like the "-Recurse" switch on the dir/Get-ChildItem command. .EXAMPLE PS C:\> Get-MgaMailFolder -Filter "My*" -User "max.master@contoso.onmicrosoft.com" -Token $Token Retrieves all folders where name starts with My in the mailbox of "max.master@contoso.onmicrosoft.com", using the connection token stored in $Token. .EXAMPLE PS C:\> Get-MgaMailFolder -ResultSize 5 Retrieves only the first 5 folders in the mailbox of the connected user. #> [CmdletBinding(DefaultParameterSetName = 'Default')] [OutputType([MSGraph.Exchange.Mail.Folder])] param ( [Parameter(ParameterSetName = 'ByFolderName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)] [Alias('FolderName', 'InputObject', 'DisplayName', 'Id')] [MSGraph.Exchange.Mail.FolderParameter[]] $Name, [switch] $IncludeChildFolders, [switch] $Recurse, [string] $Filter = "*", [string] $User, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Mail.Read" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand if ($Recurse) { $IncludeChildFolders = $true } #region helper subfunctions function invoke-internalMgaGetMethod ($invokeParam, [int]$level, [MSGraph.Exchange.Mail.Folder]$parentFolder, [String]$FunctionName) { # Subfunction for query objects and creating valid new objects from the query result $folderData = Invoke-MgaRestMethodGet @invokeParam foreach ($folderOutput in $folderData) { New-MgaMailFolderObject -RestData $folderOutput -ParentFolder $parentFolder -Level $level #-FunctionName $FunctionName } } function get-childfolder ($output, [int]$level, $invokeParam) { $FoldersWithChilds = $output | Where-Object ChildFolderCount -gt 0 $childFolders = @() do { $level = $level + 1 foreach ($folderItem in $FoldersWithChilds) { if ($folderItem.ChildFolderCount -gt 0) { Write-PSFMessage -Level VeryVerbose -Message "Getting childfolders for folder '$($folderItem.Name)'" -Tag "QueryData" $invokeParam.Field = "mailFolders/$($folderItem.Id)/childFolders" $childFolderOutput = invoke-internalMgaGetMethod -invokeParam $invokeParam -level $level -parentFolder $folderItem -FunctionName $MyInvocation.MyCommand $FoldersWithChilds = $childFolderOutput | Where-Object ChildFolderCount -gt 0 $childFolders = $childFolders + $childFolderOutput } } } while ($Recurse -and $FoldersWithChilds) $childFolders } #endregion helper subfunctions } process { Write-PSFMessage -Level VeryVerbose -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" switch ($PSCmdlet.ParameterSetName) { "Default" { $baseLevel = 1 $invokeParam = @{ "Field" = 'mailFolders' "Token" = $Token "User" = Resolve-UserString -User $User "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } $output = invoke-internalMgaGetMethod -invokeParam $invokeParam -level $baseLevel -FunctionName $MyInvocation.MyCommand | Where-Object displayName -Like $Filter if ($output -and $IncludeChildFolders) { $childFolders = $output | Where-Object ChildFolderCount -gt 0 | ForEach-Object { get-childfolder -output $_ -level $baseLevel -invokeParam $invokeParam } if ($childFolders) { [array]$output = [array]$output + $childFolders } } if (-not $output) { Stop-PSFFunction -Message "Unexpected error. Could not query root folders from user '$($User)'." -Tag "QueryData" -EnableException $true } } "ByFolderName" { foreach ($folder in $Name) { $baseLevel = 1 Write-PSFMessage -Level VeryVerbose -Message "Getting folder '$( if($folder.Name){$folder.Name}else{$folder.Id} )'" -Tag "ParameterSetHandling" $invokeParam = @{ "Token" = $Token "User" = Resolve-UserString -User $User "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } if ($folder.id) { $invokeParam.add("Field", "mailFolders/$($folder.Id)") } else { $invokeParam.add("Field", "mailFolders?`$filter=DisplayName eq '$($folder.Name)'") } $output = invoke-internalMgaGetMethod -invokeParam $invokeParam -level $baseLevel -FunctionName $MyInvocation.MyCommand | Where-Object displayName -Like $Filter if ($output -and $IncludeChildFolders) { $childFolders = get-childfolder -output $output -level $baseLevel -invokeParam $invokeParam if ($childFolders) { [array]$output = [array]$output + $childFolders } } if (-not $output) { Write-PSFMessage -Level Warning -Message "Folder '$($folder)' not found." -Tag "QueryData" } } } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } $output } end { } } function Move-MgaMailFolder { <# .SYNOPSIS Move folder(s) to another folder .DESCRIPTION Move folder(s) to another folder in Exchange Online using the graph api. .PARAMETER Folder Carrier object for Pipeline input. Accepts folders and strings. .PARAMETER DestinationFolder The destination folder where to move the folder to. Tab completion is available on this parameter for a list of well known folders. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational folders will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Move-MgaMailFolder -Folder $folder -DestinationFolder $destinationFolder Moves the folder(s) in variable $folder to the folder in the variable $destinationFolder. also possible: PS C:\> $folder | Move-MgaMailFolder -DestinationFolder $destinationFolder The variable $folder can be represent: PS C:\> $folder = Get-MgaMailFolder -Name "MyFolder" The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" .EXAMPLE PS C:\> Move-MgaMailFolder -Id $folder.id -DestinationFolder $destinationFolder.id Moves folders into the folder $destinationFolder. The variable $folder can be represent: PS C:\> $folder = Get-MgaMailFolder -Name "MyFolder" The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [Alias()] [OutputType([MSGraph.Exchange.Mail.Folder])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('InputObject', 'FolderId', 'Id')] [MSGraph.Exchange.Mail.FolderParameter[]] $Folder, [Parameter(Mandatory = $true, Position = 1)] [Alias('DestinationObject', 'DestinationFolderId')] [MSGraph.Exchange.Mail.FolderParameter] $DestinationFolder, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region checking DestinationFolder and query folder if required if ($DestinationFolder.TypeName -like "System.String") { $DestinationFolder = Resolve-MailObjectFromString -Object $DestinationFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $DestinationFolder) { throw } } $User = Resolve-UserInMailObject -Object $DestinationFolder -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking DestinationFolder and query folder if required $bodyJSON = @{ destinationId = $DestinationFolder.Id } | ConvertTo-Json } process { Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($folderItem in $Folder) { #region checking input object type and query folder if required if ($folderItem.TypeName -like "System.String") { $folderItem = Resolve-MailObjectFromString -Object $folderItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $folderItem) { continue } } $User = Resolve-UserInMailObject -Object $folderItem -User $User -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($pscmdlet.ShouldProcess("Folder '$($folderItem)'", "Move to '$($DestinationFolder)'")) { Write-PSFMessage -Tag "FolderUpdate" -Level Verbose -Message "Move folder '$($folderItem)' into folder '$($DestinationFolder)'" $invokeParam = @{ "Field" = "mailFolders/$($folderItem.Id)/move" "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $output = Invoke-MgaRestMethodPost @invokeParam if ($PassThru) { New-MgaMailFolderObject -RestData $output -ParentFolder $DestinationFolder.InputObject -FunctionName $MyInvocation.MyCommand } } } } } function New-MgaMailFolder { <# .SYNOPSIS Creates a folder in Exchange Online using the graph api. .DESCRIPTION Creates a new folder in Exchange Online using the graph api. .PARAMETER Name The name to be set as new name. .PARAMETER ParentFolder The folder where the new folder should be created in. Do not specify to create a folder on the root level. Possible values are a valid folder Id or a Mga folder object passed in. Tab completion is available on this parameter for a list of well known folders. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> New-MgaMailFolder -Name 'MyFolder' Creates a new folder named "MyFolder" on the root level of the mailbox .EXAMPLE PS C:\> New-MgaMailFolder -Name 'MyFolder' -ParentFolder $folder Creates a new folder named "MyFolder" inside the folder passed in with the variable $folder #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.Exchange.Mail.Folder])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('FolderName', 'DisplayName')] [string[]] $Name, [Parameter(Mandatory = $false, Position = 1)] [Alias('Parent', 'ParentFolderId')] [MSGraph.Exchange.Mail.FolderParameter[]] $ParentFolder, [string] $User, [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region checking input object type and query folder if required if ($ParentFolder.TypeName -like "System.String") { $ParentFolder = Resolve-MailObjectFromString -Object $ParentFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $ParentFolder) { throw } } if ($ParentFolder) { $User = Resolve-UserInMailObject -Object $ParentFolder -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand } #endregion checking input object type and query message if required } process { Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($NameItem in $Name) { if ($pscmdlet.ShouldProcess($NameItem, "New")) { $msg = "Creating subfolder '$($NameItem)'" if ($ParentFolder) { $msg = $msg + " in '$($ParentFolder)'"} Write-PSFMessage -Tag "FolderCreation" -Level Verbose -Message $msg $bodyJSON = @{ displayName = $NameItem } | ConvertTo-Json $invokeParam = @{ "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } if ($ParentFolder.Id) { $invokeParam.Add("Field", "mailFolders/$($ParentFolder.Id)/childFolders") } else { $invokeParam.Add("Field", "mailFolders") } $output = Invoke-MgaRestMethodPost @invokeParam New-MgaMailFolderObject -RestData $output -ParentFolder $ParentFolder.InputObject -FunctionName $MyInvocation.MyCommand } } } end { } } function Remove-MgaMailFolder { <# .SYNOPSIS Remove folder(s) in Exchange Online using the graph api. .DESCRIPTION Remove folder(s) in Exchange Online using the graph api. ATTENTION! The command does what it is name to! The folder will not be moved to 'deletedObjects', it will be deleted. .PARAMETER Folder The folder to be removed. This can be a name of the folder, it can be the Id of the folder or it can be a folder object passed in. Tab completion is available on this parameter for a list of well known folders. .PARAMETER Force If specified the user will not prompted on confirmation. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Remove-MgaMailFolder -Name 'MyFolder' Removes folder named "MyFolder". The folder has to be on the root level of the mailbox to be specified by individual name. .EXAMPLE PS C:\> Remove-MgaMailFolder -Name $folder Removes folder represented by the variable $folder. You will be prompted for confirmation. The variable $folder can be represent: PS C:\> $folder = Get-MgaMailFolder -Folder "MyFolder" .EXAMPLE PS C:\> $folder | Remove-MgaMailFolder -Force Removes folder represented by the variable $folder. ATTENTION, There will be NO prompt for confirmation! The variable $folder can be represent: PS C:\> $folder = Get-MgaMailFolder -Folder "MyFolder" #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([MSGraph.Exchange.Mail.Folder])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('FolderName', 'FolderId', 'InputObject', 'DisplayName', 'Name', 'Id')] [MSGraph.Exchange.Mail.FolderParameter[]] $Folder, [switch] $Force, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($folderItem in $Folder) { #region checking input object type and query folder if required if ($folderItem.TypeName -like "System.String") { if (($folderItem.IsWellKnownName -and $folderItem.Id -like "recoverableitemsdeletions") -or $folderItem.name -like "recoverableitemsdeletions") { Write-PSFMessage -Level Important -Message "Can not delete well known folder 'recoverableitemsdeletions'. Continue without action on folder." continue } $folderItem = Resolve-MailObjectFromString -Object $folderItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $folderItem) { continue } } $User = Resolve-UserInMailObject -Object $folderItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess($folderItem, "Remove (ATTENTION! Folder will not be moved to 'deletedObjects')") } if ($doAction) { Write-PSFMessage -Tag "FolderRemove" -Level Verbose -Message "Remove folder '$($folderItem)'" $invokeParam = @{ "Field" = "mailFolders/$($folderItem.Id)" "User" = $User "Body" = "" "ContentType" = "application/json" "Token" = $Token "Force" = $true "FunctionName" = $MyInvocation.MyCommand } $null = Invoke-MgaRestMethodDelete @invokeParam if ($PassThru) { $folderItem.InputObject } } } } end { } } function Rename-MgaMailFolder { <# .SYNOPSIS Rename folder(s) in Exchange Online using the graph api. .DESCRIPTION Change the displayname of folder(s) in Exchange Online using the graph api. .PARAMETER Folder The folder to be renamed. This can be a name of the folder, it can be the Id of the folder or it can be a folder object passed in. .PARAMETER NewName The name to be set as new name. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Rename-MgaMailFolder -Folder 'Inbox' -NewName 'MyPersonalInbox' Rename the "wellknown" folder inbox (regardless of it's current name), to 'MyPersonalInbox'. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.Exchange.Mail.Folder])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByInputObject')] [Alias('FolderName', 'FolderId', 'InputObject', 'DisplayName', 'Name', 'Id')] [MSGraph.Exchange.Mail.FolderParameter[]] $Folder, [Parameter(Mandatory = $true, Position = 1)] [string] $NewName, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand $bodyJSON = @{ displayName = $NewName } | ConvertTo-Json } process { Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($folderItem in $Folder) { #region checking input object type and query folder if required if ($folderItem.TypeName -like "System.String") { $folderItem = Resolve-MailObjectFromString -Object $folderItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $folderItem) { continue } } $User = Resolve-UserInMailObject -Object $folderItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($pscmdlet.ShouldProcess("Folder '$($folderItem)'", "Rename to '$($NewName)'")) { Write-PSFMessage -Tag "FolderUpdate" -Level Verbose -Message "Rename folder '$($folderItem)' to name '$($NewName)'" $invokeParam = @{ "Field" = "mailFolders/$($folderItem.Id)" "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $output = Invoke-MgaRestMethodPatch @invokeParam if ($PassThru) { New-MgaMailFolderObject -RestData $output -ParentFolder $folderItem.InputObject.ParentFolder -FunctionName $MyInvocation.MyCommand } } } } end { } } function Add-MgaMailMessageForward { <# .SYNOPSIS Forward message(s) in Exchange Online using the graph api. .DESCRIPTION Creates forward message(s) and save it as draft message(s). Alternatively, the command can directly forward a message by specifing recipient(s) and text The message is then saved in the Sent Items folder. .PARAMETER Message Carrier object for Pipeline input. This can be the id of the message or a message object passed in. .PARAMETER Comment The body of the message. .PARAMETER ToRecipients The To recipients for the message. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/message-createforward?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/message-forward?view=graph-rest-1.0 .EXAMPLE PS C:\> $mail | Add-MgaMailMessageForward Create forward message(s) and save it in the drafts folder for messages from variable $mail. also possible: PS C:\> Add-MgaMailMessageForward -Message $mail The variable $mail can be represent: PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail" .EXAMPLE PS C:\> $mail | Add-MgaMailMessageForward -ToRecipients 'someone@something.org' -Comment 'For your information.' This one directly forwards message(s) from variable $mail. The message(s) is saved in the sendItems folder The variable $mail can be represent: PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail" #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [Alias('Add-MgaMailForwardMessage')] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] #, ParameterSetName = 'ByInputObject' [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Mandatory = $true, ParameterSetName = 'DirectReply')] [Alias('To', 'Recipients')] [string[]] $ToRecipients, [Parameter(Mandatory = $true, ParameterSetName = 'DirectReply')] [Alias('Body', 'Text', 'ReplyText')] [String] $Comment, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [Parameter(ParameterSetName = 'DirectReply')] [switch] $PassThru ) begin { if ($PSCmdlet.ParameterSetName -like 'DirectReply') { $requiredPermission = "Mail.Send" } else { $requiredPermission = "Mail.ReadWrite" } $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Debug -Message "Working on parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand if ($PSCmdlet.ParameterSetName -like 'DirectReply') { $bodyJSON = New-JsonMailObject -ToRecipients $ToRecipients -Comment $Comment -FunctionName $MyInvocation.MyCommand $msgAction = "Send" } else { $bodyJSON = "" $msgAction = "create" } #endregion checking input object type and query message if required #region send message $msg = $msgAction + " reply$(if($ReplyAll){" all"})" if ($pscmdlet.ShouldProcess($messageItem, $msg)) { Write-PSFMessage -Tag "MessageReply$msgAction" -Level Verbose -Message "$($msg) message for '$($messageItem)'" $invokeParam = @{ "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } switch ($PSCmdlet.ParameterSetName) { 'Default' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/createForward") } 'DirectReply' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/forward") } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } $output = Invoke-MgaRestMethodPost @invokeParam if ($PSCmdlet.ParameterSetName -like 'Default' -and $output) { New-MgaMailMessageObject -RestData $output -FunctionName $MyInvocation.MyCommand } elseif ($PSCmdlet.ParameterSetName -like 'DirectReply' -and $PassThru) { Write-PSFMessage -Tag "MessageQuery" -Level Verbose -Message "PassThru specified, query forward message from sentItems folder." Get-MgaMailMessage -FolderName Sentitems -Subject "FW: $($messageItem.Name)" -ResultSize 5 } } #endregion send message } } end { } } function Add-MgaMailMessageReply { <# .SYNOPSIS Create reply (all) message(s) in Exchange Online using the graph api. .DESCRIPTION Create reply (all) message(s) and save it as draft message(s). Alternatively, the command can directly send the reply (all) by specifing a text The message is then saved in the Sent Items folder. .PARAMETER Message Carrier object for Pipeline input. This can be the id of the message or a message object passed in. .PARAMETER Comment The body of the message. .PARAMETER ReplyAll Creates a reply all message. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/message-createreply?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/message-reply?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/message-createreplyall?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/message-replyall?view=graph-rest-1.0 .EXAMPLE PS C:\> $mail | Add-MgaMailMessageReply Create reply message(s) for messages in variable $mail. also possible: PS C:\> Add-MgaMailMessageReply -Message $mail The variable $mail can be represent: PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail" .EXAMPLE PS C:\> $mail | Add-MgaMailMessageReply -Comment 'Reply for confirmation to your message.' This one directly send reply message(s) for messages from variable $mail. The message(s) is saved in the sendItems folder The variable $mail can be represent: PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail" #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [Alias('Add-MgaMailReplyMessage')] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] #, ParameterSetName = 'ByInputObject' [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Mandatory = $true, ParameterSetName = 'DirectReply')] [Alias('Body', 'Text', 'ReplyText')] [String] $Comment, [Alias('All')] [switch] $ReplyAll, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [Parameter(ParameterSetName = 'DirectReply')] [switch] $PassThru ) begin { if ($PSCmdlet.ParameterSetName -like 'DirectReply') { $requiredPermission = "Mail.Send" } else { $requiredPermission = "Mail.ReadWrite" } $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Debug -Message "Working on parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand if ($PSCmdlet.ParameterSetName -like 'DirectReply') { $bodyJSON = @{ comment = $Comment } | ConvertTo-Json $msgAction = "Send" } else { $bodyJSON = "" $msgAction = "create" } #endregion checking input object type and query message if required #region send message $msg = $msgAction + " reply$(if($ReplyAll){" all"})" if ($pscmdlet.ShouldProcess($messageItem, $msg)) { Write-PSFMessage -Tag "MessageReply$msgAction" -Level Verbose -Message "$($msg) message for '$($messageItem)'" $invokeParam = @{ "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } switch ($PSCmdlet.ParameterSetName) { 'Default' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/createReply$(if($ReplyAll){"All"})") } 'DirectReply' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/reply$(if($ReplyAll){"All"})") } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } $output = Invoke-MgaRestMethodPost @invokeParam if ($PSCmdlet.ParameterSetName -like 'Default' -and $output) { New-MgaMailMessageObject -RestData $output -FunctionName $MyInvocation.MyCommand } elseif ($PSCmdlet.ParameterSetName -like 'DirectReply' -and $PassThru) { Write-PSFMessage -Tag "MessageQuery" -Level Verbose -Message "PassThru specified, query reply message from sentItems folder." Get-MgaMailMessage -FolderName Sentitems -Subject "RE: $($messageItem.Name)" -ResultSize 5 } } #endregion send message } } end { } } function Copy-MgaMailMessage { <# .SYNOPSIS Copy message(s) to a folder .DESCRIPTION Copy message(s) to a folder in Exchange Online using the graph api. .PARAMETER Message Carrier object for Pipeline input. Accepts messages and strings. .PARAMETER DestinationFolder The destination folder where to copy the message to .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> $mails | Copy-MgaMailMessage -DestinationFolder $destinationFolder Copys messages in variable $mails to the folder in the variable $destinationFolder. also possible: PS C:\> Copy-MgaMailMessage -Message $mails -DestinationFolder $destinationFolder The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1 The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" .EXAMPLE PS C:\> Copy-MgaMailMessage -Id $mails.id -DestinationFolder $destinationFolder Copys messages into the folder $destinationFolder. The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1 The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" .EXAMPLE PS C:\> Get-MgaMailMessage -Folder Inbox | Copy-MgaMailMessage -DestinationFolder $destinationFolder Copys ALL messages from your inbox into the folder $destinationFolder. The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [Alias()] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Mandatory = $true, Position = 1)] [MSGraph.Exchange.Mail.FolderParameter] $DestinationFolder, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region checking DestinationFolder and query folder if required if ($DestinationFolder.TypeName -like "System.String") { $DestinationFolder = Resolve-MailObjectFromString -Object $DestinationFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $DestinationFolder) { throw } } #endregion checking DestinationFolder and query folder if required $bodyHash = @{ destinationId = ($DestinationFolder.Id | ConvertTo-Json) } } process { Write-PSFMessage -Level Debug -Message "Gettings messages by parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" # Put parameters (JSON Parts) into a valid JSON-object together $bodyJSON = Merge-HashToJSON $bodyHash #region copy messages foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($pscmdlet.ShouldProcess("message '$($messageItem)'", "Copy to folder '$($DestinationFolder.Name)'")) { Write-PSFMessage -Tag "MessageUpdate" -Level Verbose -Message "Copy message '$($messageItem)' to folder '$($DestinationFolder)'" $invokeParam = @{ "Field" = "messages/$($messageItem.Id)/copy" "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $output = Invoke-MgaRestMethodPost @invokeParam if ($PassThru) { New-MgaMailMessageObject -RestData $output } } } #endregion Update messages } } function Get-MgaMailMessage { <# .SYNOPSIS Retrieves messages from a email folder from Exchange Online using the graph api. .DESCRIPTION Retrieves messages from a email folder from Exchange Online using the graph api. .PARAMETER InputObject Carrier object for Pipeline input Accepts messages or folders from other Mga-functions .PARAMETER FolderName The display name of the folder to search. Defaults to the inbox. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Subject The subject to filter by (Client Side filtering) .PARAMETER Delta Indicates a "delta-query" for incremental changes on mails. The switch allows you to query mutliple times against the same user and folder while only getting additional, updated or deleted messages. Please notice, that delta queries needs to be handeled right. See the examples for correct usage. .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaMailMessage Return emails in the inbox of the user connected to through a token. .EXAMPLE PS C:\> $mails = Get-MgaMailMessage -Delta Return emails in the inbox of the user connected to through a token and write the output in the variable $mails. IMPORTANT, the -Delta switch needs to be specified on the first call, because the outputobject will has to be piped into the next delta query. The content of $mails can be used and processed: PS C:\> $mails So the second Get-MgaMailMessage call has to be: PS C:\> $deltaMails = Get-MgaMailMessage -InputObject $mails -Delta This return only unqueried, updated, or new messages from the previous call and writes the result in the variable $deltaMails. The content of the $deltaMails variable can be used as output and should only overwrites the $mail variable if there is content in $deltaMails: PS C:\> if($deltaMails) { $mails = $deltaMails $deltaMails } From the second call, the procedure can be continued as needed, only updates will be outputted by Get-MgaMailMessage. .EXAMPLE PS C:\> Get-MgaMailFolder -Name "Junkemail" | Get-MgaMailMessage Return emails from the Junkemail folder of the user connected to through a token. .EXAMPLE PS C:\> Get-MgaMailMessage -FolderName "MyFolder" -Subject "Important*" Return emails where the subject starts with "Important" from the folder "MyFolder" of the user connected to through a token. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(DefaultParameterSetName = 'ByInputObject')] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(ParameterSetName = 'ByInputObject', Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('Input', 'Id')] [MSGraph.Exchange.Mail.MessageOrFolderParameter[]] $InputObject, [Parameter(ParameterSetName = 'ByFolderName', Position = 0)] [Alias('FolderId', 'Folder')] [string[]] $FolderName, [string] $User, [string] $Subject = "*", [switch] $Delta, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Mail.Read" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand $InvokeParams = @() } process { Write-PSFMessage -Level VeryVerbose -Message "Gettings mails by parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" if ($PSCmdlet.ParameterSetName -like "ByInputObject" -and -not $InputObject) { Write-PSFMessage -Level Verbose -Message "No InputObject specified. Gettings mail from default folder (inbox)." -Tag "ParameterSetHandling" [MSGraph.Exchange.Mail.MessageOrFolderParameter]$InputObject = [MSGraph.Exchange.Mail.WellKnownFolder]::Inbox.ToString() } if ($PSCmdlet.ParameterSetName -like "ByFolderName") { foreach ($folderItem in $FolderName) { $folderItem = [MSGraph.Exchange.Mail.MessageOrFolderParameter]$folderItem if ($folderItem.Name -and (-not $folderItem.IsWellKnownName)) { [MSGraph.Exchange.Mail.MessageOrFolderParameter]$folderItem = Get-MgaMailFolder -Name $folderItem.Name -User $User -Token $Token } $InputObject = $InputObject + $folderItem } } foreach ($InputObjectItem in $InputObject) { Write-PSFMessage -Level VeryVerbose -Message "Parsing input $($InputObjectItem.TypeName) object '$($InputObjectItem)'" switch ($InputObjectItem.TypeName) { "MSGraph.Exchange.Mail.Message" { if ($Delta -and ('@odata.deltaLink' -in $InputObjectItem.InputObject.BaseObject.psobject.Properties.Name)) { # if delta message, construct a delta query from mail Write-PSFMessage -Level VeryVerbose -Message "Delta parameter specified and delta message found. Checking on message '$($InputObjectItem)' from the pipeline" $invokeParam = @{ "deltaLink" = $InputObjectItem.InputObject.BaseObject.'@odata.deltaLink' "Token" = $Token "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } } else { # if non delta message is parsed in, the message will be queried again (refreshed) # Not really necessary, but works as intend from pipeline usage Write-PSFMessage -Level VeryVerbose -Message "Refresh message '$($InputObjectItem)' from the pipeline" $invokeParam = @{ "Field" = "messages/$($InputObjectItem.Id)" "User" = $InputObjectItem.InputObject.BaseObject.User "Token" = $Token "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } if ($Delta) { $invokeParam.Add("Delta", $true) } } $invokeParams = $invokeParams + $invokeParam } "MSGraph.Exchange.Mail.Folder" { Write-PSFMessage -Level VeryVerbose -Message "Gettings messages in folder '$($InputObjectItem)' from the pipeline" $invokeParam = @{ "Field" = "mailFolders/$($InputObjectItem.Id)/messages" "User" = $InputObjectItem.InputObject.User "Token" = $Token "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } if ($Delta) { $invokeParam.Add("Delta", $true) } $invokeParams = $invokeParams + $invokeParam } "System.String" { $invokeParam = @{ "User" = $User "Token" = $Token "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } if ($Delta) { $invokeParam.Add("Delta", $true) } $name = if ($InputObjectItem.IsWellKnownName) { $InputObjectItem.Name } else { $InputObjectItem.Id } if ($name.length -eq 152 -or $name.length -eq 136) { # Id is a message Write-PSFMessage -Level VeryVerbose -Message "Gettings messages with Id '$($InputObjectItem)'" -Tag "InputValidation" $invokeParam.Add("Field", "messages/$($name)") } elseif ($name.length -eq 120 -or $name.length -eq 104) { # Id is a folder Write-PSFMessage -Level VeryVerbose -Message "Gettings messages in folder with Id '$($InputObjectItem)'" -Tag "InputValidation" $invokeParam.Add("Field", "mailFolders/$($name)/messages") } elseif ($InputObjectItem.IsWellKnownName -and $name) { # a well known folder is specified by name $invokeParam.Add("Field", "mailFolders/$($name)/messages") } else { # not a valid Id -> should not happen Write-PSFMessage -Level Warning -Message "The specified Id seeams not be a valid Id. Skipping object '$($name)'" -Tag "InputValidation" continue } $invokeParams = $invokeParams + $invokeParam Remove-Variable -Name name -Force -ErrorAction Ignore -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false } Default { Write-PSFMessage -Level Critical -Message "Failed on type validation. Can not handle $($InputObjectItem.TypeName)" -EnableException $true -Tag "TypeValidation" } } } } end { $fielList = @() $InvokeParamsUniqueList = @() Write-PSFMessage -Level Verbose -Message "Checking $( ($InvokeParams | Measure-Object).Count ) objects on unique calls..." foreach ($invokeParam in $InvokeParams) { if ($invokeParam.Field -and ($invokeParam.Field -notin $fielList)) { $InvokeParamsUniqueList = $InvokeParamsUniqueList + $invokeParam $fielList = $fielList + $invokeParam.Field } elseif ($invokeParam.deltaLink -notin $fielList) { $InvokeParamsUniqueList = $InvokeParamsUniqueList + $invokeParam $fielList = $fielList + $invokeParam.deltaLink } } Write-PSFMessage -Level Verbose -Message "Invoking $( ($InvokeParamsUniqueList | Measure-Object).Count ) REST calls for gettings messages" # run the message query and process the output foreach ($invokeParam in $InvokeParamsUniqueList) { $data = Invoke-MgaRestMethodGet @invokeParam | Where-Object { $_.subject -like $Subject } $output = foreach ($messageOutput in $data) { New-MgaMailMessageObject -RestData $messageOutput } } if ($output) { $output } else { Write-PSFMessage -Level Warning -Message "Message not found." -Tag "QueryData" } } } function Move-MgaMailMessage { <# .SYNOPSIS Move message(s) to a folder .DESCRIPTION Move message(s) to a folder in Exchange Online using the graph api. .PARAMETER Message Carrier object for Pipeline input. Accepts messages and strings. .PARAMETER DestinationFolder The destination folder where to move the message to .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> $mails | Move-MgaMailMessage -DestinationFolder $destinationFolder Moves messages in variable $mails to the folder in the variable $destinationFolder. also possible: PS C:\> Move-MgaMailMessage -Message $mails -DestinationFolder $destinationFolder The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1 The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" .EXAMPLE PS C:\> Move-MgaMailMessage -Id $mails.id -DestinationFolder $destinationFolder Moves messages into the folder $destinationFolder. The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1 The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" .EXAMPLE PS C:\> Get-MgaMailMessage -Folder Inbox | Move-MgaMailMessage -DestinationFolder $destinationFolder Moves ALL messages from your inbox into the folder $destinationFolder. The variable $destinationFolder can be represent: PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive" #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [Alias()] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(Mandatory = $true, Position = 1)] [MSGraph.Exchange.Mail.FolderParameter] $DestinationFolder, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region checking DestinationFolder and query folder if required if ($DestinationFolder.TypeName -like "System.String") { $DestinationFolder = Resolve-MailObjectFromString -Object $DestinationFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $DestinationFolder) { throw } } #endregion checking DestinationFolder and query folder if required $bodyHash = @{ destinationId = ($DestinationFolder.Id | ConvertTo-Json) } } process { Write-PSFMessage -Level Debug -Message "Gettings messages by parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" # Put parameters (JSON Parts) into a valid JSON-object together and output the result $bodyJSON = Merge-HashToJson $bodyHash #region move message foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($pscmdlet.ShouldProcess("message '$($messageItem)'", "Move to folder '$($DestinationFolder.Name)'")) { Write-PSFMessage -Tag "MessageUpdate" -Level Verbose -Message "Move message '$($messageItem)' to folder '$($DestinationFolder)'" $invokeParam = @{ "Field" = "messages/$($messageItem.Id)/move" "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $output = Invoke-MgaRestMethodPost @invokeParam if ($PassThru) { New-MgaMailMessageObject -RestData $output } } } #endregion move message } end { } } function New-MgaMailMessage { <# .SYNOPSIS Creates a folder in Exchange Online using the graph api. .DESCRIPTION Creates a new folder in Exchange Online using the graph api. .PARAMETER Folder The folder where the new folder should be created in. Do not specify to create a folder on the root level. Possible values are a valid folder Id or a Mga folder object passed in. Tab completion is available on this parameter for a list of well known folders. .PARAMETER Subject The subject of the new message. .PARAMETER Sender The account that is actually used to generate the message. (Updatable only when sending a message from a shared mailbox or sending a message as a delegate. In any case, the value must correspond to the actual mailbox used.) .PARAMETER From The mailbox owner and sender of the message. Must correspond to the actual mailbox used. .PARAMETER ToRecipients The To recipients for the message. .PARAMETER CCRecipients The Cc recipients for the message. .PARAMETER BCCRecipients The Bcc recipients for the message. .PARAMETER ReplyTo The email addresses to use when replying. .PARAMETER Body The body of the message. .PARAMETER Categories The categories associated with the message. .PARAMETER Importance The importance of the message. The possible values are: Low, Normal, High. .PARAMETER InferenceClassification The classification of the message for the user, based on inferred relevance or importance, or on an explicit override. The possible values are: focused or other. .PARAMETER InternetMessageId The message ID in the format specified by RFC2822. .PARAMETER IsDeliveryReceiptRequested Indicates whether a delivery receipt is requested for the message. .PARAMETER IsReadReceiptRequested Indicates whether a read receipt is requested for the message. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information go to: https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0 .LINK .EXAMPLE PS C:\> New-MgaMailMessage -ToRecipients 'someone@something.org' -Subject 'A new Mail' -Body 'This is a new mail' Creates a new message in the drafts folder .EXAMPLE PS C:\> New-MgaMailMessage -Subject 'A new Mail' -Folder 'MyFolder' Creates a new message in the folder named "MyFolder" #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0)] [Alias('Name', 'Title')] [string[]] $Subject, [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithFolder')] [Alias('FolderName', 'FolderId')] [MSGraph.Exchange.Mail.FolderParameter] $Folder, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $Sender, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $From, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [Alias('To', 'Recipients')] [string[]] $ToRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $CCRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $BCCRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $ReplyTo, [String] $Body, [String[]] $Categories, [ValidateSet("Low", "Normal", "High")] [String] $Importance, [ValidateSet("focused", "other")] [String] $InferenceClassification, [String] $InternetMessageId, [bool] $IsDeliveryReceiptRequested, [bool] $IsReadReceiptRequested, [string] $User, [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region checking input object type and query folder if required if ($Folder.TypeName -like "System.String") { $Folder = Resolve-MailObjectFromString -Object $Folder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand if (-not $Folder) { throw } } if ($Folder) { $User = Resolve-UserInMailObject -Object $Folder -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand } #endregion checking input object type and query message if required } process { Write-PSFMessage -Level Debug -Message "Creating message(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" #region Put parameters (JSON Parts) into a valid "message"-JSON-object together $jsonParams = @{} $names = "Subject", "Sender", "From", "ToRecipients", "CCRecipients", "BCCRecipients", "ReplyTo", "Body", "Categories", "Importance", "InferenceClassification", "IsDeliveryReceiptRequested", "IsReadReceiptRequested" foreach ($name in $names) { if (Test-PSFParameterBinding -ParameterName $name) { Write-PSFMessage -Level Debug -Message "Add $($name) from parameters to message" -Tag "ParameterParsing" $jsonParams.Add($name, (Get-Variable $name -Scope 0).Value) } } $bodyJSON = New-JsonMailObject @jsonParams -FunctionName $MyInvocation.MyCommand #endregion Put parameters (JSON Parts) into a valid "message"-JSON-object together #region create messages if ($pscmdlet.ShouldProcess($Subject, "New")) { $msg = "Creating message '$($Subject)'" if ($Folder) { $msg = $msg + " in '$($Folder)'" } else { $msg = $msg + " in drafts folder" } Write-PSFMessage -Level Verbose -Message $msg -Tag "MessageCreation" $invokeParam = @{ "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } if ($Folder.Id) { $invokeParam.Add("Field", "mailFolders/$($Folder.Id)/messages") } else { $invokeParam.Add("Field", "messages") } $output = Invoke-MgaRestMethodPost @invokeParam if ($output) { New-MgaMailMessageObject -RestData $output -FunctionName $MyInvocation.MyCommand } } #endregion create messages } end { } } function Remove-MgaMailMessage { <# .SYNOPSIS Remove message(s) in Exchange Online using the graph api. .DESCRIPTION Remove message(s) in Exchange Online using the graph api. ATTENTION! The command does what it is name to! The message will not be moved to 'deletedObjects', it will be deleted. .PARAMETER Message The message to be removed. This can be the id of the message or a message object passed in. .PARAMETER Force If specified the user will not prompted on confirmation. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Remove-MgaMailMessage -Message $message Removes message represented by the variable $message. This example will purge (the first 100 messages in) the inbox. You will be prompted for confirmation. The variable $message can be represent: PS C:\> $message = Get-MgaMailMessage -Folder Inbox .EXAMPLE PS C:\> $message | Remove-MgaMailMessage -Force Removes message represented by the variable $message. This example will purge (the first 100 messages in) the inbox. ATTENTION, there will be NO prompt for confirmation! The variable $mails can be represent: PS C:\> $message = Get-MgaMailMessage -Folder Inbox #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [switch] $Force, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Debug -Message "Gettings message(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess($messageItem, "Remove (ATTENTION! Message will not be moved to 'deletedObjects')") } if ($doAction) { Write-PSFMessage -Tag "MessageRemove" -Level Verbose -Message "Remove message '$($messageItem)'" $invokeParam = @{ "Field" = "messages/$($messageItem.Id)" "User" = $User "Body" = "" "ContentType" = "application/json" "Token" = $Token "Force" = $true "FunctionName" = $MyInvocation.MyCommand } $null = Invoke-MgaRestMethodDelete @invokeParam if ($PassThru) { $messageItem.InputObject } } } } end { } } function Send-MgaMailMessage { <# .SYNOPSIS Send message(s) in Exchange Online using the graph api. .DESCRIPTION Send a previously created draft message(s) and save the message in the SendItems folder. Alternatively, the command can directly send a message by specifing a recipient, subject, ... .PARAMETER Message Carrier object for Pipeline input. This can be the id of the message or a message object passed in. .PARAMETER Subject The subject of the new message. .PARAMETER Sender The account that is actually used to generate the message. (Updatable only when sending a message from a shared mailbox or sending a message as a delegate. In any case, the value must correspond to the actual mailbox used.) .PARAMETER From The mailbox owner and sender of the message. Must correspond to the actual mailbox used. .PARAMETER ToRecipients The To recipients for the message. .PARAMETER CCRecipients The Cc recipients for the message. .PARAMETER BCCRecipients The Bcc recipients for the message. .PARAMETER ReplyTo The email addresses to use when replying. .PARAMETER Body The body of the message. .PARAMETER Categories The categories associated with the message. .PARAMETER Importance The importance of the message. The possible values are: Low, Normal, High. .PARAMETER InferenceClassification The classification of the message for the user, based on inferred relevance or importance, or on an explicit override. The possible values are: focused or other. .PARAMETER IsDeliveryReceiptRequested Indicates whether a delivery receipt is requested for the message. .PARAMETER IsReadReceiptRequested Indicates whether a read receipt is requested for the message. .PARAMETER SaveToSentItems Indicates whether to save the message in Sent Items. Only needed to be specified if the parameter should be $false, default is $true. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0 https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0 .EXAMPLE PS C:\> $mail | Send-MgaMailMessage Send message(s) in variable $mail. also possible: PS C:\> Send-MgaMailMessage -Message $mail The variable $mail can be represent: PS C:\> $mail = New-MgaMailMessage -ToRecipients 'someone@something.org' -Subject 'A new Mail' -Body 'This is a new mail' #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'DirectSend')] [Alias()] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByInputObject')] [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [Parameter(ParameterSetName = 'DirectSend', Mandatory = $true)] [Alias('Name', 'Title')] [string[]] $Subject, [Parameter(ParameterSetName = 'DirectSend')] [String] $Body, [Parameter(ParameterSetName = 'DirectSend', Mandatory = $true)] [Alias('To', 'Recipients')] [string[]] $ToRecipients, [Parameter(ParameterSetName = 'DirectSend')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $CCRecipients, [Parameter(ParameterSetName = 'DirectSend')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $Sender, [Parameter(ParameterSetName = 'DirectSend')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $From, [Parameter(ParameterSetName = 'DirectSend')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $BCCRecipients, [Parameter(ParameterSetName = 'DirectSend')] [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $ReplyTo, [Parameter(ParameterSetName = 'DirectSend')] [String[]] $Categories, [Parameter(ParameterSetName = 'DirectSend')] [ValidateSet("Low", "Normal", "High")] [String] $Importance = "Normal", [Parameter(ParameterSetName = 'DirectSend')] [ValidateSet("focused", "other")] [String] $InferenceClassification = "other", [Parameter(ParameterSetName = 'DirectSend')] [bool] $IsDeliveryReceiptRequested = $false, [Parameter(ParameterSetName = 'DirectSend')] [bool] $IsReadReceiptRequested = $false, [Parameter(ParameterSetName = 'DirectSend')] [bool] $SaveToSentItems = $true, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.Send" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Debug -Message "Working on parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" #region send message switch ($PSCmdlet.ParameterSetName) { 'ByInputObject' { foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required #region send message if ($pscmdlet.ShouldProcess($messageItem, "Send")) { Write-PSFMessage -Tag "MessageSend" -Level Verbose -Message "Send message '$($messageItem)'" $invokeParam = @{ "Field" = "messages/$($messageItem.Id)/send" "User" = $User "Body" = "" "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $null = Invoke-MgaRestMethodPost @invokeParam if ($PassThru) { $messageItem.InputObject } } #endregion send message } } 'DirectSend' { #region Put parameters (JSON Parts) into a valid "message"-JSON-object together $jsonParams = @{} $names = "Subject", "Sender", "From", "ToRecipients", "CCRecipients", "BCCRecipients", "ReplyTo", "Body", "Categories", "Importance", "InferenceClassification", "IsDeliveryReceiptRequested", "IsReadReceiptRequested" foreach ($name in $names) { if (Test-PSFParameterBinding -ParameterName $name) { Write-PSFMessage -Level Debug -Message "Add $($name) from parameters to message" -Tag "ParameterParsing" $jsonParams.Add($name, (Get-Variable $name -Scope 0).Value) } } $bodyHash = @{ "message" = (New-JsonMailObject @jsonParams -FunctionName $MyInvocation.MyCommand) "saveToSentItems" = ($SaveToSentItems | ConvertTo-Json) } $bodyJSON = Merge-HashToJson $bodyHash #endregion Put parameters (JSON Parts) into a valid "message"-JSON-object together #region send message if ($pscmdlet.ShouldProcess($Subject, "Send")) { Write-PSFMessage -Tag "MessageSend" -Level Verbose -Message "Send message with subject '$($Subject)' to recipient '$($ToRecipients)'" $invokeParam = @{ "Field" = "sendMail" "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $null = Invoke-MgaRestMethodPost @invokeParam if ($PassThru) { $messageItem.InputObject } } #endregion send message } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } #endregion send message } end { } } function Set-MgaMailMessage { <# .SYNOPSIS Set properties on message(s) .DESCRIPTION Set properties on message(s) in Exchange Online using the graph api. .PARAMETER Message Carrier object for Pipeline input. Accepts messages. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER IsRead Indicates whether the message has been read. .PARAMETER Subject The subject of the message. (Updatable only if isDraft = true.) .PARAMETER Sender The account that is actually used to generate the message. (Updatable only if isDraft = true, and when sending a message from a shared mailbox, or sending a message as a delegate. In any case, the value must correspond to the actual mailbox used.) .PARAMETER From The mailbox owner and sender of the message. Must correspond to the actual mailbox used. (Updatable only if isDraft = true.) .PARAMETER ToRecipients The To recipients for the message. (Updatable only if isDraft = true.) .PARAMETER CCRecipients The Cc recipients for the message. (Updatable only if isDraft = true.) .PARAMETER BCCRecipients The Bcc recipients for the message. (Updatable only if isDraft = true.) .PARAMETER ReplyTo The email addresses to use when replying. (Updatable only if isDraft = true.) .PARAMETER Body The body of the message. (Updatable only if isDraft = true.) .PARAMETER Categories The categories associated with the message. .PARAMETER Importance The importance of the message. The possible values are: Low, Normal, High. .PARAMETER InferenceClassification The classification of the message for the user, based on inferred relevance or importance, or on an explicit override. The possible values are: focused or other. .PARAMETER InternetMessageId The message ID in the format specified by RFC2822. (Updatable only if isDraft = true.) .PARAMETER IsDeliveryReceiptRequested Indicates whether a delivery receipt is requested for the message. .PARAMETER IsReadReceiptRequested Indicates whether a read receipt is requested for the message. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the token to the console .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> $mail | Set-MgaMailMessage -IsRead $false Set messages represented by variable $mail to status "unread" The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1 .EXAMPLE PS C:\> $mail | Set-MgaMailMessage -IsRead $false -categories "Red category" Set status "unread" and category "Red category" to messages represented by variable $mail The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1 .EXAMPLE PS C:\> $mail | Set-MgaMailMessage -ToRecipients "someone@something.org" Set reciepent from draft mail represented by variable $mail The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Drafts .EXAMPLE PS C:\> Set-MgaMailMessage -Id $mail.Id -ToRecipients "someone@something.org" -Subject "Something important" Set reciepent from draft mail represented by variable $mail The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Drafts .EXAMPLE PS C:\> $mail | Set-MgaMailMessage -ToRecipients $null Clear reciepent from draft mail represented by variable $mail The variable $mails can be represent: PS C:\> $mails = Get-MgaMailMessage -Folder Drafts #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')] [Alias("Update-MgaMailMessage")] [OutputType([MSGraph.Exchange.Mail.Message])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('InputObject', 'MessageId', 'Id')] [MSGraph.Exchange.Mail.MessageParameter[]] $Message, [ValidateNotNullOrEmpty()] [bool] $IsRead, [string] $Subject, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $Sender, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string] $From, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [Alias('To', 'Recipients')] [string[]] $ToRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $CCRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $BCCRecipients, [AllowNull()] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $ReplyTo, [String] $Body, [String[]] $Categories, [ValidateSet("Low", "Normal", "High")] [String] $Importance, [ValidateSet("focused", "other")] [String] $InferenceClassification, [String] $InternetMessageId, [bool] $IsDeliveryReceiptRequested, [bool] $IsReadReceiptRequested, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "Mail.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" #region Put parameters (JSON Parts) into a valid "message"-JSON-object together $jsonParams = @{} $boundParameters = @() $names = "IsRead", "Subject", "Sender", "From", "ToRecipients", "CCRecipients", "BCCRecipients", "ReplyTo", "Body", "Categories", "Importance", "InferenceClassification", "InternetMessageId", "IsDeliveryReceiptRequested", "IsReadReceiptRequested" foreach ($name in $names) { if (Test-PSFParameterBinding -ParameterName $name) { Write-PSFMessage -Level Debug -Message "Add $($name) from parameters to message" -Tag "ParameterParsing" $boundParameters = $boundParameters + $name $jsonParams.Add($name, (Get-Variable $name -Scope 0).Value) } } $bodyJSON = New-JsonMailObject @jsonParams -FunctionName $MyInvocation.MyCommand #endregion Put parameters (JSON Parts) into a valid "message"-JSON-object together #region Update messages foreach ($messageItem in $Message) { #region checking input object type and query message if required if ($messageItem.TypeName -like "System.String") { $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $messageItem) { continue } } $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand #endregion checking input object type and query message if required if ($pscmdlet.ShouldProcess("message '$($messageItem)'", "Update properties '$([string]::Join("', '", $boundParameters))'")) { Write-PSFMessage -Tag "MessageUpdate" -Level Verbose -Message "Update properties '$([string]::Join("', '", $boundParameters))' on message '$($messageItem)'" $invokeParam = @{ "Field" = "messages/$($messageItem.Id)" "User" = $User "Body" = $bodyJSON "ContentType" = "application/json" "Token" = $Token "FunctionName" = $MyInvocation.MyCommand } $output = Invoke-MgaRestMethodPatch @invokeParam if ($output -and $PassThru) { New-MgaMailMessageObject -RestData $output } } } #endregion Update messages } } function Get-MgaMailboxSetting { <# .SYNOPSIS Get the mailbox settings from Exchange Online using the graph api. .DESCRIPTION Get the mailbox settings from Exchange Online using the graph api. This includes settings for automatic replies (notify people automatically upon receipt of their email), locale (language and country/region), and time zone, and working hours. You can view all mailbox settings, or get specific settings by specifing switch parameters. .PARAMETER AutomaticReplySetting If specified, only the settings for automatic notifications to senders of an incoming email are outputted. Fun fact: Here's an interesting historical question - when we say Out of Office, why does it sometimes get shortened to ‘OOF’? Shouldn’t it be ‘OOO’? https://blogs.technet.microsoft.com/exchange/2004/07/12/why-is-oof-an-oof-and-not-an-ooo/ .PARAMETER LanguageSetting If specified, only the information about the locale, including the preferred language and country/region are displayed. .PARAMETER TimeZoneSetting If specified, only the timezone settings from the users mailbox are displayed. .PARAMETER WorkingHoursSetting If specified, only the settings for the days of the week and hours in a specific time zone that the user works are displayed. .PARAMETER ArchiveFolderSetting If specified, only the archive folder settings from the users mailbox are displayed. .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/user-get-mailboxsettings?view=graph-rest-1.0 .EXAMPLE PS C:\> Get-MgaMailboxSetting Return all mailbox settings for the user connected to through the registered token. .EXAMPLE PS C:\> Get-MgaMailboxSetting -AutomaticReplySetting Return only the settings for automatic notifications to senders of an incoming email for the user connected to through the registered token. .EXAMPLE PS C:\> Get-MgaMailboxSetting -LanguageSetting Return only the information about the locale, including the preferred language and country/region, for the user connected to through the registered token. .EXAMPLE PS C:\> Get-MgaMailboxSetting -TimeZoneSetting Return only the timezone settings for the user connected to through the registered token. .EXAMPLE PS C:\> Get-MgaMailboxSetting -WorkingHoursSetting Return only the settings for the days of the week and hours in a specific time zone the user connected to through the registered token works. .EXAMPLE PS C:\> Get-MgaMailboxSetting -ArchiveFolderSetting Return only the settings for the folder where mails are archived in the user connected to through the registered token works. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'AllSettings')] param ( [Parameter(ParameterSetName = 'AutomaticReplySetting')] [Alias('AutoReply', 'OutOfOffice', 'OutOfOfficeSetting', 'OOFSettings', 'OOF')] [switch] $AutomaticReplySetting, [Parameter(ParameterSetName = 'LanguageSetting')] [Alias('Language')] [switch] $LanguageSetting, [Parameter(ParameterSetName = 'TimeZoneSetting')] [Alias('TimeZone')] [switch] $TimeZoneSetting, [Parameter(ParameterSetName = 'WorkingHoursSetting')] [Alias('WorkingsHour')] [switch] $WorkingHoursSetting, [Parameter(ParameterSetName = 'ArchiveFolderSetting')] [Alias('ArchiveFolder')] [switch] $ArchiveFolderSetting, [string] $User, [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "MailboxSettings.Read" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand } process { Write-PSFMessage -Level Verbose -Message "Getting mailbox settings for '$(Resolve-UserString -User $User)' by ParameterSet $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" #region query data $invokeParam = @{ "Token" = $Token "User" = $User "FunctionName" = $MyInvocation.MyCommand } switch ($PSCmdlet.ParameterSetName) { {$_ -like 'AllSettings' -or $_ -like 'ArchiveFolderSetting'} { $invokeParam.Add('Field', 'mailboxSettings') } #'AllSettings' { $invokeParam.Add('Field', 'mailboxSettings') } 'AutomaticReplySetting' { $invokeParam.Add('Field', 'mailboxSettings/automaticRepliesSetting') } 'LanguageSetting' { $invokeParam.Add('Field', 'mailboxSettings/language') } 'TimeZoneSetting' { $invokeParam.Add('Field', 'mailboxSettings/timeZone') } 'WorkingHoursSetting' { $invokeParam.Add('Field', 'mailboxSettings/workingHours') } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } $data = Invoke-MgaRestMethodGet @invokeParam #endregion query data #region output data foreach ($output in $data) { $mailboxSettingObject = New-MgaMailboxSettingObject -RestData $output -Type $PSCmdlet.ParameterSetName -User $User -Token $Token -FunctionName $MyInvocation.MyCommand $mailboxSettingObject } #endregion output data } end { } } function Set-MgaMailboxSetting { <# .SYNOPSIS Set the mailbox settings from Exchange Online using the graph api. .DESCRIPTION set the mailbox settings in Exchange Online using the graph api. This includes settings for automatic replies (notify people automatically upon receipt of their email), locale (language and country/region), and time zone, and working hours. You can parse in modified settings from Get-MgaMailboxSetting command. .PARAMETER InputObject Carrier object for Pipeline input. Accepts all the different setting objects outputted by Get-MgaMailboxSetting. .PARAMETER AutomaticReply If specified, the command will set AutomaticReply settings .PARAMETER Language If specified, the command will set Language settings .PARAMETER TimeZone If specified, the command will set TimeZone settings .PARAMETER WorkingHours If specified, the command will set WorkingHour settings .PARAMETER User The user-account to access. Defaults to the main user connected as. Can be any primary email name of any user the connected token has access to. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .PARAMETER PassThru Outputs the mailbox settings to the console. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .NOTES For addiontional information about Microsoft Graph API go to: https://docs.microsoft.com/en-us/graph/api/user-update-mailboxsettings?view=graph-rest-1.0 .EXAMPLE PS C:\> Set-MgaMailboxSetting Return all mailbox settings for the user connected to through the registered token. .EXAMPLE PS C:\> Set-MgaMailboxSetting -AutomaticReply Set .EXAMPLE PS C:\> Set-MgaMailboxSetting -Language Set .EXAMPLE PS C:\> Set-MgaMailboxSetting -TimeZone Set .EXAMPLE PS C:\> Get-MgaMailboxSetting -WorkingHours Set #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'InputObject')] param ( [Parameter(ParameterSetName = 'InputObject')] [Alias('MailboxSetting', 'ArchiveFolderSetting', 'AutomaticReplySetting', 'LanguageSetting', 'TimeZoneSetting', 'WorkingHoursSetting', 'Setting', 'SettingObject')] [MSGraph.Exchange.MailboxSetting.MailboxSettingParameter] $InputObject, [Parameter(ParameterSetName = 'AutomaticReplySetting')] [Alias()] [switch] $AutomaticReply, [Parameter(ParameterSetName = 'LanguageSetting')] [Alias()] [switch] $Language, [Parameter(ParameterSetName = 'TimeZoneSetting')] [Alias()] [switch] $TimeZone, [Parameter(ParameterSetName = 'WorkingHoursSetting')] [Alias()] [switch] $WorkingHours, [string] $User, [MSGraph.Core.AzureAccessToken] $Token, [switch] $PassThru ) begin { $requiredPermission = "MailboxSettings.ReadWrite" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand $invokeParam = @{ "Field" = "mailboxSettings" "Token" = $Token "User" = $User "FunctionName" = $MyInvocation.MyCommand } } process { #region prepare rest data switch ($PSCmdlet.ParameterSetName) { 'InputObject' { Write-PSFMessage -Level Verbose -Message "Working on mailbox settings '$($InputObject)' for '$(Resolve-UserString -User $User)' by ParameterSet $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" $User = Resolve-UserInMailObject -Object $InputObject -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand $bodyJSON = New-JsonMailboxSettingObject -SettingObject $InputObject -User $User -FunctionName $MyInvocation.MyCommand $invokeParam.Add('Body', $bodyJSON) } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } #endregion prepare rest data if ($pscmdlet.ShouldProcess("mailbox of '$(Resolve-UserString -User $User -ContextData)'", "Set $InputObject")) { # set data $data = Invoke-MgaRestMethodPatch @invokeParam #region output data if($PassThru) { foreach ($output in $data) { $mailboxSettingObject = New-MgaMailboxSettingObject -RestData $output -Type $InputObject.Name -User $User -Token $Token -FunctionName $MyInvocation.MyCommand $mailboxSettingObject } } #endregion output data } } end { } } function Get-MgaTeam { <# .SYNOPSIS Get Microsoft Teams Team .DESCRIPTION Get Microsoft Teams Team(s) with current settings via Microsoft Graph API The command can gather teams where the current connected user is joined, or list all existing teams in the tenant. Detailed settings for a team are only showed, if the connected user has appropriate permissions for the team. .PARAMETER Name The name of the team(s) to query. (Client Side filtering) .PARAMETER Id The Id of the team(s) to query. (Client Side filtering) .PARAMETER InputObject A team object piped in to refresh data. .PARAMETER ListAll Show all available teams in the whole tenant. As default behaviour, only teams where the current user is joined will be shown. .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaTeam Returns all teams the connected user is joined to. .EXAMPLE PS C:\> Get-MgaTeam -Name "MyTeam*" Returns all teams starting with name "MyTeam" where the connected user is joined to. .EXAMPLE PS C:\> Get-MgaTeam -InputObject $team Returns refreshed info for the team out of the variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> Get-MgaTeam -ListAll Returns all teams in the tenant. Detailed information about configuration for the team is only listed, when the connected user has appropriate permissions (administrative permissions or joined member of the team). If the user has permissions is indicated by the property "Accessible". .EXAMPLE PS C:\> Get-MgaTeam -Name "Sales*" -ListAll Returns all teams in the tenant starting with name "Sales". Detailed information about configuration for the team is only listed, when the connected user has appropriate permissions (administrative permissions or joined member of the team). If the user has permissions is indicated by the property "Accessible". .EXAMPLE PS C:\> Get-MgaTeam -ResultSize 5 -Token $Token Retrieves only the first 5 teams for the connected user with the token represented in the variable $token. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'ByName')] [OutputType([MSGraph.Teams.Team])] param ( [Parameter(ParameterSetName = 'ByInputOBject', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)] [Alias('Team')] [MSGraph.Teams.TeamParameter[]] $InputObject, [Parameter(ParameterSetName = 'ByName', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false, Position = 0)] [Parameter(ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)] [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')] [string] $Name, [Parameter(ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)] [Parameter(ParameterSetName = 'ById', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)] [Alias('IdFilter', 'FilterId')] [string] $Id, [Parameter(ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)] [switch] $ListAll, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Group.Read.All" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand $requiredPermission = "Team.ReadBasic.All" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region helper subfunctions function invoke-internalMgaGetTeamsDetail ([psobject[]]$teamList, $token, $resultSize, [String]$functionName) { # Subfunction for query team information foreach ($teamListItem in $teamList) { Write-PSFMessage -Level VeryVerbose -Message "Getting details on team '$($teamListItem.displayName)'" -Tag "QueryData" -FunctionName $functionName $invokeParam = @{ "Field" = "teams/$($teamListItem.id)" "Token" = $token "UserUnspecific" = $true "ResultSize" = $resultSize "FunctionName" = $functionName } $teamInfo = [PSCustomObject]@{ } try { $teamInfo = Invoke-MgaRestMethodGet @invokeParam } catch { Write-PSFMessage -Level VeryVerbose -Message "Unable to query information on team '$($teamListItem.displayName)'. Assuming no permission or membership." -Tag "QueryData" -FunctionName $functionName $teamInfo | Add-Member -MemberType NoteProperty -Name id -Value $teamListItem.id $teamInfo | Add-Member -MemberType NoteProperty -Name isArchived -Value $teamListItem.isArchived $teamInfo | Add-Member -MemberType NoteProperty -Name User -Value $teamListItem.User } $teamInfo | Add-Member -MemberType NoteProperty -Name displayName -Value "$($teamListItem.displayName)" -ErrorAction SilentlyContinue $teamInfo | Add-Member -MemberType NoteProperty -Name description -Value "$($teamListItem.description)" -ErrorAction SilentlyContinue $teamInfo } } #endregion helper subfunctions } process { Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" [array]$data = @() $invokeParam = @{ "Token" = $Token "ResultSize" = $ResultSize "FunctionName" = $MyInvocation.MyCommand } switch ($PSCmdlet.ParameterSetName) { { $_ -in 'ByName', 'ById' } { Write-PSFMessage -Level Verbose -Message "Gettings joined team(s) for user $($token.UserprincipalName)" -Tag "QueryData" $invokeParam.Add('Field', 'me/joinedTeams') $invokeParam.Add('UserUnspecific', $true) [array]$teamList = Invoke-MgaRestMethodGet @invokeParam if ($PSCmdlet.ParameterSetName -like 'ByName' -and $Name) { [array]$teamList = $teamList | Where-Object displayName -Like $Name } if ($PSCmdlet.ParameterSetName -like 'ById' -and $Id) { [array]$teamList = $teamList | Where-Object Id -Like $Id } if ($teamList) { Write-PSFMessage -Level VeryVerbose -Message "Found $($teamList.Count) team(s) for user $($token.UserprincipalName)" -Tag "QueryData" $teamList = invoke-internalMgaGetTeamsDetail -teamList $teamList -token $Token -resultSize $ResultSize -functionName $MyInvocation.MyCommand $teamList = $teamList | Add-Member -MemberType NoteProperty -Name "InfoFromJoinedTeam" -Value $true -PassThru $data = $data + $teamList } else { Stop-PSFFunction -Message "No joined teams found for user $($token.TokenOwner)" -Tag "QueryData" } } 'ListAll' { Write-PSFMessage -Level Verbose -Message "Gettings all team(s) from the tenant" -Tag "QueryData" #Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData" $invokeParam.Add('Field', "groups?`$select=id,displayname,description,resourceProvisioningOptions") $invokeParam.Add('UserUnspecific', $true) [array]$teamList = Invoke-MgaRestMethodGet @invokeParam | Where-Object resourceProvisioningOptions -like "Team" if ($Name) { [array]$teamList = $teamList | Where-Object displayName -Like $Name } if ($Id) { [array]$teamList = $teamList | Where-Object Id -Like $Id } if ($teamList) { Write-PSFMessage -Level VeryVerbose -Message "Found $($teamList.Count) team(s) in tenant" -Tag "QueryData" $teamList = invoke-internalMgaGetTeamsDetail -teamList $teamList -token $Token -resultSize $ResultSize -functionName $MyInvocation.MyCommand $teamList = $teamList | Add-Member -MemberType NoteProperty -Name "InfoFromJoinedTeam" -Value $false -PassThru $data = $data + $teamList } else { Stop-PSFFunction -Message "Unexpected Error while getting all teams team information from the tenant." -Tag "QueryData" } } 'ByInputOBject' { foreach ($team in $InputOBject) { Write-PSFMessage -Level Verbose -Message "Getting team '$($team)'" -Tag "ParameterSetHandling" # resolve team via name if ($team.TypeName -like "System.String") { if ($team.Name) { # get team by name $teamQueried = Get-MgaTeam -Name $team.Name -ListAll -ResultSize 0 -Token $Token } else { # get team by Id $teamQueried = Get-MgaTeam -Id $team.Id -ListAll -ResultSize 0 -Token $Token } } else { # a previsouly query team is piped in if ($team.InputObject.InfoFromJoinedTeam) { $teamQueried = Get-MgaTeam -Name $team.Name -Token $Token } else { $teamQueried = Get-MgaTeam -Name $team.Name -ListAll -ResultSize 0 -Token $Token } } if ($teamQueried) { $teamQueried } else { Stop-PSFFunction -Message "Unexpected Error while getting information on team '$($team)'" -Tag "QueryData" } } } Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand } } #region output data Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData" foreach ($output in $data) { if ($output.memberSettings) { # team object with accessible information $teamObject = [MSGraph.Teams.Team]::new( $output.id, $output.internalId, $output.displayName, $output.description, $output.user, $output.isArchived, $output.InfoFromJoinedTeam, $output.webUrl, $output ) $memberSetting = [MSGraph.Teams.TeamMemberSettings]::new( $output.memberSettings.allowCreateUpdateChannels, $output.memberSettings.allowDeleteChannels, $output.memberSettings.allowAddRemoveApps, $output.memberSettings.allowCreateUpdateRemoveTabs, $output.memberSettings.allowCreateUpdateRemoveConnectors ) $teamObject.memberSettings = $memberSetting $guestSettings = [MSGraph.Teams.TeamGuestSettings]::new( $output.guestSettings.allowCreateUpdateChannels, $output.guestSettings.allowDeleteChannels ) $teamObject.guestSettings = $guestSettings $messagingSettings = [MSGraph.Teams.TeamMessagingSettings]::new( $output.messagingSettings.allowUserEditMessages, $output.messagingSettings.allowUserDeleteMessages, $output.messagingSettings.allowOwnerDeleteMessages, $output.messagingSettings.allowTeamMentions, $output.messagingSettings.allowChannelMentions ) $teamObject.messagingSettings = $messagingSettings $funSettings = [MSGraph.Teams.TeamFunSettings]::new( $output.funSettings.allowGiphy, $output.funSettings.giphyContentRating, $output.funSettings.allowStickersAndMemes, $output.funSettings.allowCustomMemes ) $teamObject.funSettings = $funSettings } else { # minimal team object. Basically, just information about the group $teamObject = [MSGraph.Teams.Team]::new( $output.id, $output.displayName, $output.description, $output.user, $output.isArchived, $output.InfoFromJoinedTeam ) } Write-PSFMessage -Level Debug -Message "Output new object '$($teamObject)'." -Tag "OutputData" $teamObject } #endregion output data } end { } } function Get-MgaTeamChannel { <# .SYNOPSIS Get channels from a Microsoft Teams Team .DESCRIPTION Get channel(s) from Microsoft Teams Team(s) with current settings via Microsoft Graph API .PARAMETER InputObject A team object piped in. .PARAMETER Name The name of the team(s) to query. (Client Side filtering) .PARAMETER Id The Id of the team(s) to query. (Client Side filtering) .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaTeamChannel $team Returns all channels from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> $team | Get-MgaTeamChannel -Name "General" Returns the Channel "General" from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> $team | Get-MgaTeamChannel -ResultSize 5 Retrieves only the first 5 channels from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam #> [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.Teams.TeamChannel])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('Team', 'TeamName', 'TeamID')] [MSGraph.Teams.TeamParameter[]] $InputObject, [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')] [string] $Name, [Alias('FilterId', 'IdFilter')] [string] $Id, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Group.Read.All" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region helper subfunctions #endregion helper subfunctions } process { Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) channel by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" #Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData" foreach ($teamItem in $InputObject) { #region checking input object type and query message if required if ($teamItem.psobject.TypeNames[0] -like "System.String") { $teamItem = Resolve-MailObjectFromString -Object $teamItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $teamItem) { continue } } #endregion checking input object type and query message if required #region query data $invokeParam = @{ "Field" = "teams/$($teamItem.Id)/channels" "Token" = $Token 'UserUnspecific' = $true "ResultSize" = $ResultSize "ApiVersion" = "v1.0" "FunctionName" = $MyInvocation.MyCommand } Write-PSFMessage -Level Verbose -Message "Getting channel from team '$($teamItem)'" -Tag "QueryData" $data = Invoke-MgaRestMethodGet @invokeParam if ($Name) { [array]$data = $data | Where-Object displayName -Like $Name } if ($Id) { [array]$data = $data | Where-Object Id -Like $Id } #endregion query data #region output data Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData" foreach ($output in $data) { if($output.Email) { $_email = [mailaddress]::new($output.Email) } else { $_email = $null } $outputObject = [MSGraph.Teams.TeamChannel]::new( $output.Id, $output.DisplayName, $output.Description, $output.isFavoriteByDefault, $output.WebUrl, $_email, $output.User, $output ) Write-PSFMessage -Level Debug -Message "Output channel '$($outputObject)'." -Tag "OutputData" $outputObject } #endregion output data } } end { } } function Get-MgaTeamMember { <# .SYNOPSIS Get members from a Microsoft Teams Team .DESCRIPTION Get members from Microsoft Teams Team(s) via Microsoft Graph API .PARAMETER InputObject A team object where to get members from. .PARAMETER Name Name filter for the members to query. (Client Side filtering) .PARAMETER Id Id filter for the members to query. (Client Side filtering) .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaTeamMember $team Returns all members from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> $team | Get-MgaTeamMember -Name "*John*" Returns any member contains "John" in his name from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> $team | Get-MgaTeamMember -ResultSize 5 Retrieves only the first 5 members from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam #> [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.AzureAD.Users.User])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('Team', 'TeamName', 'TeamID')] [MSGraph.Teams.TeamParameter[]] $InputObject, [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')] [string] $Name, [Alias('FilterId', 'IdFilter')] [string] $Id, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Group.Read.All" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region helper subfunctions #endregion helper subfunctions } process { Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) member by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData" foreach ($teamItem in $InputObject) { #region checking input object type and query message if required if ($teamItem.TypeName -like "System.String") { $teamItem = Resolve-MailObjectFromString -Object $teamItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $teamItem) { continue } } #endregion checking input object type and query message if required #region query data $invokeParam = @{ "Field" = "groups/$($teamItem.Id)/members" "Token" = $Token 'UserUnspecific' = $true "ResultSize" = $ResultSize "ApiVersion" = "beta" "FunctionName" = $MyInvocation.MyCommand } Write-PSFMessage -Level Verbose -Message "Getting team '$($teamItem)' members" -Tag "QueryData" $data = Invoke-MgaRestMethodGet @invokeParam if ($Name) { [array]$data = $data | Where-Object displayName -Like $Name } if ($Id) { [array]$data = $data | Where-Object Id -Like $Id } #endregion query data #region output data Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData" foreach ($output in $data) { $outputObject = [MSGraph.AzureAD.Users.User]::new() foreach($prop in ($output | Get-Member -MemberType NoteProperty | Where-Object name -notlike "extension_*" | Where-Object name -notlike "@*").Name) { if($output.$prop) { $outputObject.$prop = $output.$prop } } Write-PSFMessage -Level Debug -Message "Output member '$($outputObject)'." -Tag "OutputData" $outputObject } #endregion output data } } end { } } function Get-MgaTeamOwner { <# .SYNOPSIS Get owner(s) of a Microsoft Teams Team .DESCRIPTION Get owner(s) of a Microsoft Teams Team(s) via Microsoft Graph API .PARAMETER InputObject A team object where to get owner(s) from. .PARAMETER Name Name filter for the owner(s) to query. (Client Side filtering) .PARAMETER Id Id filter for the owners(s) to query. (Client Side filtering) .PARAMETER ResultSize The amount of objects to query within API calls to MSGraph. To avoid long waitings while query a large number of items, the graph api only query a special amount of items within one call. A value of 0 represents "unlimited" and results in query all items wihtin a call. The default is 100. .PARAMETER Token The token representing an established connection to the Microsoft Graph Api. Can be created by using New-MgaAccessToken. Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken. .EXAMPLE PS C:\> Get-MgaTeamOwner $team Returns all members from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> $team | Get-MgaTeamOwner -Name "*John*" Returns any member contains "John" in his name from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam .EXAMPLE PS C:\> $team | Get-MgaTeamOwner -ResultSize 5 Retrieves only the first 5 members from team in variable $team. Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam #> [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')] [OutputType([MSGraph.AzureAD.Users.User])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('Team', 'TeamName', 'TeamID')] [MSGraph.Teams.TeamParameter[]] $InputObject, [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')] [string] $Name, [Alias('FilterId', 'IdFilter')] [string] $Id, [Int64] $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100), [MSGraph.Core.AzureAccessToken] $Token ) begin { $requiredPermission = "Group.Read.All" $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand #region helper subfunctions #endregion helper subfunctions } process { Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) owner(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling" Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData" foreach ($teamItem in $InputObject) { #region checking input object type and query message if required if ($teamItem.TypeName -like "System.String") { $teamItem = Resolve-MailObjectFromString -Object $teamItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand if (-not $teamItem) { continue } } #endregion checking input object type and query message if required #region query data $invokeParam = @{ "Field" = "groups/$($teamItem.Id)/owners" "Token" = $Token 'UserUnspecific' = $true "ResultSize" = $ResultSize "ApiVersion" = "beta" "FunctionName" = $MyInvocation.MyCommand } Write-PSFMessage -Level Verbose -Message "Getting team '$($teamItem)' owner(s)" -Tag "QueryData" $data = Invoke-MgaRestMethodGet @invokeParam if ($Name) { [array]$data = $data | Where-Object displayName -Like $Name } if ($Id) { [array]$data = $data | Where-Object Id -Like $Id } #endregion query data #region output data Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData" foreach ($output in $data) { $outputObject = [MSGraph.AzureAD.Users.User]::new() foreach($prop in ($output | Get-Member -MemberType NoteProperty | Where-Object name -notlike "extension_*" | Where-Object name -notlike "@*").Name) { if($output.$prop) { $outputObject.$prop = $output.$prop } } Write-PSFMessage -Level Debug -Message "Output owner '$($outputObject)'." -Tag "OutputData" $outputObject } #endregion output data } } end { } } <# This is the configuration file for default values in the module By default, it is enough to have a single one of them, however if you have enough configuration settings to justify having multiple copies of it, feel totally free to split them into multiple files. #> # module creation defaults - used by module creation framework (PSModuleDevelopment) Set-PSFConfig -Module 'MSGraph' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." Set-PSFConfig -Module 'MSGraph' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." #region Settings inside the module # Azure Active Directory App Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Application.ClientID' -Value "5e79add2-6288-4d91-bebc-cae920227404" -Initialize -Validation 'string' -Description "Well known ClientID from registered Application in Azure tenant" Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Application.RedirectUrl' -Value "https://login.microsoftonline.com/common/oauth2/nativeclient" -Initialize -Validation 'string' -Description "Redirection URL specified in MS Azure Application portal for the registered application" Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Application.DefaultPermission' -Value @("Mail.ReadWrite.Shared") -Initialize -Validation 'string' -Description "The default permission to consent when getting a token" Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.ApiConnection' -Value "https://graph.microsoft.com" -Initialize -Validation 'string' -Description "The App ID URI of the target web API (secured resource). To find the App ID URI, in the Azure Portal, click Azure Active Directory, click Application registrations, open the application's Settings page, then click Properties." Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.ApiVersion' -Value "v1.0" -Initialize -Validation 'string' -Description "Specifies the API version used to query objects in Microsoft Graph. For more information goto https://docs.microsoft.com/en-us/graph/versioning-and-support" Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Authentiation.IdentityPlatformVersion' -Value "2.0" -Initialize -Validation 'string' -Description "Specifies the endpoint version of the logon platform (Microsoft identity platform) where to connect for logon. For more information goto https://docs.microsoft.com/en-us/azure/active-directory/develop/about-microsoft-identity-platform" Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Authentiation.Endpoint' -Value "https://login.microsoftonline.com" -Initialize -Validation 'string' -Description "The URI for authentication and query tokens (access and refresh)" # web client Set-PSFConfig -Module 'MSGraph' -Name 'WebClient.UserAgentName' -Value "PowerShellModule.MSGraph.RestClient" -Initialize -Validation 'string' -Description "Name of the user agent in the web client used by module" Set-PSFConfig -Module 'MSGraph' -Name 'WebClient.UserAgentVersion' -Value "1.1" -Initialize -Validation 'string' -Description "Version for the user agent in the web client used by module" # command behavior Set-PSFConfig -Module 'MSGraph' -Name 'Query.ResultSize' -Value 100 -Initialize -Validation integer -Description "Limit of amount of records returned by a function. Use 0 for unlimited." Set-PSFConfig -Module 'MSGraph' -Name 'Hierarchy.Path.Separator' -Value "\" -Initialize -Validation string -Description "the character used to process hierarchical names (like FullName property on folders) in MSGraph module." #endregion Settings inside the module # defining module wide variables New-Variable -Name msgraph_Token -Scope Script -Visibility Public -Description "Variable for registered token. This is for convinience use with the commands in MSGraph module" -Force <# Stored scriptblocks are available in [PsfValidateScript()] attributes. This makes it easier to centrally provide the same scriptblock multiple times, without having to maintain it in separate locations. It also prevents lengthy validation scriptblocks from making your parameter block hard to read. Set-PSFScriptblock -Name 'MSGraph.ScriptBlockName' -Scriptblock { } #> <# # Example: Register-PSFTeppScriptblock -Name "MSGraph.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' } #> Register-PSFTeppScriptblock -Name "MSGraph.Exchange.Mail.WellKnowFolders" -ScriptBlock { [enum]::GetNames([MSGraph.Exchange.Mail.WellKnownFolder]) | ForEach-Object { (Get-Culture).TextInfo.ToTitleCase( $_ ) } } Register-PSFTeppScriptblock -Name "MSGraph.Exchange.Category.Colors" -ScriptBlock { [enum]::GetNames([MSGraph.Exchange.Category.ColorName]) } Register-PSFTeppScriptblock -Name "MSGraph.Core.Permission.Consent.User" -ScriptBlock { @( 'email', 'openid', 'profile', 'offline_access', 'Bookings.Manage.All', 'Bookings.Read.All', 'Bookings.ReadWrite.All', 'BookingsAppointment.ReadWrite.All', 'Calendars.Read', 'Calendars.Read.Shared', 'Calendars.ReadWrite', 'Calendars.ReadWrite.Shared', 'Contacts.Read', 'Contacts.Read.Shared', 'Contacts.ReadWrite', 'Contacts.ReadWrite.Shared', 'Device.Command', 'Device.Read', 'EAS.AccessAsUser.All', 'Files.Read', 'Files.Read.All', 'Files.Read.Selected', 'Files.ReadWrite', 'Files.ReadWrite.All', 'Files.ReadWrite.AppFolder', 'Files.ReadWrite.Selected', 'Financials.ReadWrite.All', 'Mail.Read', 'Mail.Read.Shared', 'Mail.ReadWrite', 'Mail.ReadWrite.Shared', 'Mail.Send', 'Mail.Send.Shared', 'MailboxSettings.Read', 'MailboxSettings.ReadWrite', 'Notes.Create', 'Notes.Read', 'Notes.Read.All', 'Notes.ReadWrite', 'Notes.ReadWrite.All', 'Notes.ReadWrite.CreatedByApp', 'Notifications.ReadWrite.CreatedByApp', 'People.Read', 'Sites.Manage.All', 'Sites.Read.All', 'Sites.ReadWrite.All', 'Tasks.Read', 'Tasks.Read.Shared', 'Tasks.ReadWrite', 'Tasks.ReadWrite.Shared', 'User.Read', 'User.ReadBasic.All', 'User.ReadWrite', 'UserActivity.ReadWrite.CreatedByApp', 'UserTimelineActivity.Write.CreatedByApp' ) } <# # Example: Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name MSGraph.alcohol #> Register-PSFTeppArgumentCompleter -Command New-MgaAccessToken -Parameter "Permission" -Name "MSGraph.Core.Permission.Consent.User" Register-PSFTeppArgumentCompleter -Command Get-MgaMailFolder -Parameter "Name" -Name "MSGraph.Exchange.Mail.WellKnowFolders" Register-PSFTeppArgumentCompleter -Command Move-MgaMailFolder -Parameter "DestinationFolder" -Name "MSGraph.Exchange.Mail.WellKnowFolders" Register-PSFTeppArgumentCompleter -Command New-MgaMailFolder -Parameter "ParentFolder" -Name "MSGraph.Exchange.Mail.WellKnowFolders" Register-PSFTeppArgumentCompleter -Command Remove-MgaMailFolder -Parameter "Folder" -Name "MSGraph.Exchange.Mail.WellKnowFolders" Register-PSFTeppArgumentCompleter -Command Get-MgaMailMessage -Parameter "FolderName" -Name "MSGraph.Exchange.Mail.WellKnowFolders" Register-PSFTeppArgumentCompleter -Command Move-MgaMailMessage -Parameter "DestinationFolder" -Name "MSGraph.Exchange.Mail.WellKnowFolders" Register-PSFTeppArgumentCompleter -Command Get-MgaExchCategory -Parameter "Color" -Name "MSGraph.Exchange.Category.Colors" Register-PSFTeppArgumentCompleter -Command New-MgaExchCategory -Parameter "Color" -Name "MSGraph.Exchange.Category.Colors" Register-PSFTeppArgumentCompleter -Command Set-MgaExchCategory -Parameter "Color" -Name "MSGraph.Exchange.Category.Colors" New-PSFLicense -Product 'MSGraph' -Manufacturer 'Andreas Bellstedt, Friedrich Weinmann' -ProductVersion $ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2018-08-28") -Text @" Copyright (c) 2018 Andreas Bellstedt, Friedrich Weinmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ #endregion Load compiled code |