Services/MgGraph.ps1
Function ConvertTo-IMicrosoftGraphRecipient { [CmdletBinding()] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [AllowEmptyCollection()] [AllowNull()] [pscustomobject]$EmailAddress, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string]$Name ) begin { # Return Null If Provided Recipient is Empty if (([string]::IsNullOrEmpty($EmailAddress)) -and ([string]::IsNullOrEmpty($EmailAddress.Address))) { return $null } } process { # Loop through each of the recipient paramater array objects $IMicrosoftGraphRecipient = foreach ($address in $EmailAddress) { # Check if string (email address) or object/hashtable/etc. If not, separate out. if (-not ($address.GetType().Name -eq 'String')) { # Verify object contains 'Address' key or property. if ([string]::IsNullOrEmpty($address.AddressObj)) { throw "Improperly formatted from, recipient, or reply to address." } # Set 'Name' & update 'Address' (do 'Name' 1st!) $Name = $address.Name $address = $address.AddressObj } if ([string]::IsNullOrEmpty($Name)) { @{ EmailAddress = @{Address = $address} } } else { @{ EmailAddress = [ordered]@{ Name = $Name Address = $address} } } } } end { return $IMicrosoftGraphRecipient } } function ConvertTo-IMicrosoftGraphItemBody { [CmdletBinding()] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string]$Content, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateSet('Text','HTML')] [string]$ContentType = 'Text' # The MIME type. See https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/message-format-and-transmission ) begin {} process { $IMicrosoftGraphItemBody = @{ ContentType = $ContentType Content = $Content } return $IMicrosoftGraphItemBody } end {} } Function ConvertTo-IMicrosoftGraphAttachment { [CmdletBinding()] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [AllowNull()] [array]$Attachment ) begin { if ([string]::IsNullOrEmpty($Attachment)) { return $null } } process { [array]$IMicrosoftGraphAttachment = foreach ($currentAttachment in $Attachment) { switch ($currentAttachment.GetType().Name) { 'Hashtable' { # If direct file content is supplied. $AttachmentType = 'Content' if (($currentAttachment.ContainsKey('Name')) -and $currentAttachment.ContainsKey('Content')) { $Attachment_ByteEncoded = [System.Convert]::ToBase64String($currentAttachment.Content) } else { throw "The attachment hashtable object is improperly formatted. The hashtable requires the keys of `'Name`' and `'Contents`'" } [array]$IMicrosoftGraphAttachmentItem = @{ "@odata.type" = "#microsoft.graph.fileAttachment" Name = $currentAttachment.Name ContentBytes = $Attachment_ByteEncoded } } 'String' { # If a directory or file path is supplied. if (-not (Test-Path -Path $currentAttachment)) { throw 'Invalid path to attachment directory or file.' } switch ((Get-Item -Path $currentAttachment).GetType().Name) { 'FileInfo'{ $AttachmentType = 'FilePath' $FileInfo = Get-Item -Path $currentAttachment $Attachment_ByteEncoded = [convert]::ToBase64String([System.IO.File]::ReadAllBytes($FileInfo.FullName)) [array]$IMicrosoftGraphAttachmentItem = @{ "@odata.type" = "#microsoft.graph.fileAttachment" Name = $FileInfo.Name ContentBytes = $Attachment_ByteEncoded } } 'DirectoryInfo' { $AttachmentType = 'DirectoryPath' $DirectoryContent = Get-ChildItem $currentAttachment -File -Recurse [array]$IMicrosoftGraphAttachmentItem = foreach ($file in $DirectoryContent) { $Attachment_ByteEncoded = [convert]::ToBase64String([System.IO.File]::ReadAllBytes($file.FullName)) @{ "@odata.type" = "#microsoft.graph.fileAttachment" Name = $file.Name ContentBytes = $Attachment_ByteEncoded } } } Default {throw 'Unexpected attachment object type.'} } } Default {throw 'Unexpected attachment object type.'} } $IMicrosoftGraphAttachmentItem } } end { return $IMicrosoftGraphAttachment } } function Send-ScriptMessage_MgGraph { [CmdletBinding()] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [MessageType[]]$Type, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [pscustomobject]$From, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [pscustomobject]$ReplyTo, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [AllowEmptyString()] [pscustomobject]$To, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [AllowEmptyString()] [pscustomobject]$CC, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [AllowEmptyString()] [pscustomobject]$BCC, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [bool]$SaveToSentItems = $true, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string]$Subject, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [pscustomobject]$Body, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [array]$Attachment, # Array of Content(bytes), File paths, and/or Directory paths [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string]$SenderId ) begin { # Set the Service ID. $ServiceId = 'MgGraph' } process { # Send the message on each supported service specified. foreach ($typeItem in $Type) { switch ($typeItem) { Mail { # Convert Parameters to IMicrosoft* $Message = @{} $Message['From'] = ConvertTo-IMicrosoftGraphRecipient -EmailAddress $From [array]$Message['ReplyTo'] = ConvertTo-IMicrosoftGraphRecipient -EmailAddress $ReplyTo [array]$Message['To'] = ConvertTo-IMicrosoftGraphRecipient -EmailAddress $To [array]$Message['CC'] = ConvertTo-IMicrosoftGraphRecipient -EmailAddress $CC [array]$Message['BCC'] = ConvertTo-IMicrosoftGraphRecipient -EmailAddress $BCC if (-not [string]::IsNullOrEmpty($Body.Content)) { if ([string]::IsNullOrEmpty($Body.ContentType)) # Don't send 'ContentType' if not provided. It will default to 'Text' { [hashtable]$Message['Body'] = ConvertTo-IMicrosoftGraphItemBody -Content $Body.Content } else { [hashtable]$Message['Body'] = ConvertTo-IMicrosoftGraphItemBody -Content $Body.Content -ContentType $Body.ContentType } } [array]$Message['Attachment'] = ConvertTo-IMicrosoftGraphAttachment -Attachment $Attachment # Build Email $EmailParams = [ordered]@{ SaveToSentItems = $SaveToSentItems Message = [ordered]@{ From = $Message.From ReplyTo = $Message.ReplyTo ToRecipients = $Message.To CcRecipients = $Message.CC BccRecipients = $Message.BCC Subject = $Subject Body = $Message.Body Attachments = $Message.Attachment } } # Check For Separate UserID Value if ([string]::IsNullOrEmpty($SenderId)) { $SenderId = $Message.From.emailAddress.Address } # Send Email. $SendEmailMessageResult = Send-MgUserMail -UserId $SenderId -BodyParameter $EmailParams -PassThru # Collect Return Info $SendScriptMessageResult_SentFrom = [PSCustomObject]@{ Name = $From.Name Address = $From.AddressObj } [array]$SendScriptMessageResult_Recipients_To = foreach ($i in ($Message.To).EmailAddress) {[PSCustomObject]$i} # Converts hashtables to PSCustomObjects [array]$SendScriptMessageResult_Recipients_CC = foreach ($i in ($Message.CC).EmailAddress) {[PSCustomObject]$i} # Converts hashtables to PSCustomObjects [array]$SendScriptMessageResult_Recipients_BCC = foreach ($i in ($Message.BCC).EmailAddress) {[PSCustomObject]$i} # Converts hashtables to PSCustomObjects [array]$SendScriptMessageResult_Recipients_All = @( # Since Address is also a PSMethod we need to do some fun stuff (List<psobject> doesn't have a method called Address) so we don't get the dreaded 'OverloadDefinitions'. [System.Linq.Enumerable]::ToList([PSObject[]]$SendScriptMessageResult_Recipients_To).Address [System.Linq.Enumerable]::ToList([PSObject[]]$SendScriptMessageResult_Recipients_CC).Address [System.Linq.Enumerable]::ToList([PSObject[]]$SendScriptMessageResult_Recipients_BCC).Address ) [array]$SendScriptMessageResult_Recipients_All = $SendScriptMessageResult_Recipients_All | Sort-Object -Unique # Remove duplicate items. $SendScriptMessageResult_Recipients = [PSCustomObject]@{ To = $SendScriptMessageResult_Recipients_To CC = $SendScriptMessageResult_Recipients_CC BCC = $SendScriptMessageResult_Recipients_BCC All = $SendScriptMessageResult_Recipients_All } $SendScriptMessageResult = [PSCustomObject]@{ MessageService = $ServiceId MessageType = $typeItem Status = $SendEmailMessageResult # The SDK only returns $true and nothing else (and only that because of the 'PassThru') Error = $null SentFrom = $SendScriptMessageResult_SentFrom Recipients = $SendScriptMessageResult_Recipients } # If successful, output result info. $SendScriptMessageResult } Chat { Write-Warning -Message "The '$($typeItem)' message type has not yet been implemented for service '$($ServiceId)'." } Default { Write-Warning -Message "'$($typeItem)' is an invalid message type for service '$($ServiceId)'." } } } } end {} } function Connect-ScriptMessage_MgGraph { [CmdletBinding()] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [pscustomobject]$ServiceConfig ) begin { } process { # Check For Microsoft.Graph Module # Don't import the entire 'Microsoft.Graph' module. Only import the needed sub-modules. Import-Module 'Microsoft.Graph.Authentication' -ErrorAction SilentlyContinue Import-Module 'Microsoft.Graph.Users.Actions' -ErrorAction SilentlyContinue if (!(Get-Module -Name "Microsoft.Graph.Users.Actions") -or !(Get-Module -Name "Microsoft.Graph.Authentication")) { # Module is not available. Write-Error @" Please First Install the Microsoft.Graph.Users.Actions Module from https://www.powershellgallery.com/packages/Microsoft.Graph/ ". Installing the main modules of the SDK, Microsoft.Graph, will install all sub modules for each module. Consider only installing the necessary modules, including Microsoft.Graph.Authentication which is installed by default when you opt to install the sub modules individually. For a list of available Microsoft Graph modules, use Find-Module Microsoft.Graph*. Only cmdlets for the installed modules will be available for use. Mail Requirements: Microsoft.Graph.Users.Actions Chat Requirements: Microsoft.Graph.Teams "@ Return } # Connect to the Microsoft Graph API. # E.g. Connect-MgGraph -Scopes "User.Read.All","Group.ReadWrite.All" # You can add additional permissions by repeating the Connect-MgGraph command with the new permission scopes. # View the current scopes under which the PowerShell SDK is (trying to) execute cmdlets: Get-MgContext | select -ExpandProperty Scopes # List all the scopes granted on the service principal object (you cn also do it via the Azure AD UI): Get-MgServicePrincipal -Filter "appId eq '14d82eec-204b-4c2f-b7e8-296a70dab67e'" | % { Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $_.Id } | fl # Find Graph permission needed. More info on permissions: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent) # E.g., Find-MgGraphPermission -SearchString "Teams" -PermissionType Delegated # E.g., Find-MgGraphPermission -SearchString "Teams" -PermissionType Application $MicrosoftGraphScopes = @() if ($ServiceConfig.AllowableMessageTypes -contains 'Mail') { $MicrosoftGraphScopes += @( 'Mail.Send' #'Mail.Send.Shared' # Scope is not needed at the moment. ) } if ($ServiceConfig.AllowableMessageTypes -contains 'Chat') { $MicrosoftGraphScopes += @( 'ChatMessage.Send' ) } $MgPermissionType = $ServiceConfig.MgPermissionType $MgTenantID = $ServiceConfig.MgTenantID $MgClientID = $ServiceConfig.MgClientID switch ($MgPermissionType) { Delegated { $null = Connect-MgGraph -Scopes $MicrosoftGraphScopes -TenantId $MgTenantID -ClientId $MgClientID } Application { [string]$MgApp_AuthenticationType = $ServiceConfig.MgApp_AuthenticationType if ($LoggingEnabled) {Write-PSFMessage -Message "Microsoft Graph App Authentication Type: $MgApp_AuthenticationType"} switch ($MgApp_AuthenticationType) { CertificateFile { $MgApp_CertificatePath = $ExecutionContext.InvokeCommand.ExpandString($ServiceConfig.MgApp_CertificatePath) # Try accessing private key certificate without password using current process credentials. [X509Certificate]$MgApp_Certificate = $null try { [X509Certificate]$MgApp_Certificate = Get-PfxCertificate -FilePath $MgApp_CertificatePath -NoPromptForPassword } catch # If that doesn't work try the included credentials. { $MgApp_EncryptedCertificatePassword = $ServiceConfig.MgApp_EncryptedCertificatePassword if ([string]::IsNullOrEmpty($MgApp_EncryptedCertificatePassword)) { if ($LoggingEnabled) {Write-PSFMessage -Level Error "Cannot access .pfx private key certificate file and no password has been provided."} throw $_ } else { [SecureString]$MgApp_EncryptedCertificateSecureString = $MgApp_EncryptedCertificatePassword | ConvertTo-SecureString # Can only be decrypted by the same AD account on the same computer. [X509Certificate]$MgApp_Certificate = Get-PfxCertificate -FilePath $MgApp_CertificatePath -NoPromptForPassword -Password $MgApp_EncryptedCertificateSecureString } } $null = Connect-MgGraph -TenantId $MgTenantID -ClientId $MgClientID -Certificate $MgApp_Certificate } CertificateName { $MgApp_CertificateName = $ServiceConfig.MgApp_CertificateName $null = Connect-MgGraph -TenantId $MgTenantID -ClientId $MgClientID -CertificateName $MgApp_CertificateName } CertificateThumbprint { $MgApp_CertificateThumbprint = $ServiceConfig.MgApp_CertificateThumbprint $null = Connect-MgGraph -TenantId $MgTenantID -ClientId $MgClientID -CertificateThumbprint $MgApp_CertificateThumbprint } ClientSecret { $MgApp_EncryptedSecret = $ServiceConfig.MgApp_EncryptedSecret $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $MgClientID, $($MgApp_EncryptedSecret | ConvertTo-SecureString) $null = Connect-MgGraph -TenantId $MgTenantID -ClientSecretCredential $ClientSecretCredential } Default {throw "Invalid `'MgApp_AuthenticationType`' value."} } } Default {throw "Invalid `'MgPermissionType`' value."} } } end {} } |