Notifier.psm1
$script:ModuleRoot = $PSScriptRoot function Get-Parameter { <# .SYNOPSIS Parses out all parameters on a provided scriptblock. .DESCRIPTION Parses out all parameters on a provided scriptblock. Useful when building plugin-based code, as it allows having plugin code selfdocument. .PARAMETER ScriptBlock The scriptblock to process / parse for parameters. .PARAMETER AsHashtable Return the results as a hashtable, rather than as individual objects. .EXAMPLE PS C:\> Get-Parameter -Scriptblock $code -AsHashtable Returns a hashtable containing all parameters of the provided scriptblock. #> [OutputType([hashtable])] [CmdletBinding()] param ( [scriptblock] $ScriptBlock, [switch] $AsHashtable ) process { $help = $ScriptBlock.Ast.GetHelpContent() $parameters = foreach ($parameter in $ScriptBlock.Ast.ParamBlock.Parameters) { $description = '' if ($help.Parameters.$($parameter.Name.VariablePath.UserPath.ToUpper())) { $description = $help.Parameters.$($parameter.Name.VariablePath.UserPath.ToUpper()).Trim() } $mandatoryAttribute = $parameter.Attributes.Where{ $_.TypeName.Name -eq 'Parameter' }.NamedArguments.Where{ $_.ArgumentName -eq 'Mandatory' }[0] [PSCustomObject]@{ PSTypeName = 'Notifier.Parameter' Name = $parameter.Name.VariablePath.UserPath Mandatory = $mandatoryAttribute.Argument.VariablePath.UserPath -eq 'true' -or $mandatoryAttribute.ExpressionOmitted Type = $parameter.Attributes.Where{ $_ -is [System.Management.Automation.Language.TypeConstraintAst] }.TypeName.FullName DefaultValue = $parameter.DefaultValue.Value Description = $description } } if (-not $AsHashtable) { return $parameters } $results = @{ } foreach ($parameter in $parameters) { $results[$parameter.Name] = $parameter } $results } } function Import-NotificationWorkflow { <# .SYNOPSIS Imports the settings defining whom to notify when. .DESCRIPTION Imports the settings defining whom to notify when. This defines under what condition, what provider gets triggered in what way, based on the object triggering the notification. A workflow file is a psd1 file that will be loaded in an unsafe manner (code gets executed). The resulting configuration sets / hashtables will be processed into settings. Expected Layout of each workflow configuration set: @{ WorkflowID = '<workflowid>' Subscriptions = @( @{ <subscription1> } @{ <subscription2> } @{ <subscriptionN> } ) SubscriptionPath = '<path>' ProviderPath = '<path>' } WorkflowID <string>: The ID of the workflow. When importing multiple workflows, the last with a defined value wins. Providing it is optional, but when invoking the notifications, a WorkflowID is required, either by previous configuration or by parameter. Subscriptions <hashtable[]>: A list of subscription configurations. Each subscription configuration needs to match the parameters on Register-NotificationSubscription. For more details on this, see the help on Register-NotificationSubscription. SubscriptionPath <string>: The path where additional configuration files for subscriptions are expected and loaded if present. Can be an absolute path or a path relative to the current file's location. We expect psd1 files with one or more hashtables, as if passed via the Subscriptions property. ProviderPath <string>: The path where additional notification providers are defined. Can be an absolute path or a path relative to the current file's location. We expect ps1 files that execute their own Register-NotificationProvider - but in effect, you can make it run _any_ kind of code as part of the import. Each setting is optional, especially as multiple workflow definitions can be merged. However, a Workflow ID is necessary for the overall execution and without _any_ subscriptions, nothing happens. .PARAMETER Path Path to the file(s) defining a notification workflow. .PARAMETER Append Whether any loaded settings should be added to existing settings. By default, they will replace them instead. .EXAMPLE PS C:\> Import-NotificationWorkflow -Path C:\monitoring\certexpiration\workflow.psd1 Loads the workflow configuration in "C:\monitoring\certexpiration\workflow.psd1" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [PSFFile] $Path, [switch] $Append ) begin { if (-not $Append) { # Reset all registered settings $script:_NotificationSubscriptions = @{ } $script:_CurrentWorkflowID = $null } $fileLoader = [PsfScriptBlock] { & $_ } } process { foreach ($file in $Path) { Write-PSFMessage -Message 'Importing Notification Workflow from {0}' -StringValues $file -Target $file $data = Import-PSFPowerShellDataFile -Path $file -Psd1Mode Unsafe foreach ($datum in $data) { if ($datum.WorkflowID) { Write-PSFMessage -Message 'Workflow ID: {0}' -StringValues $datum.WorkflowID -Target $file $script:_CurrentWorkflowID = $datum.WorkflowID } foreach ($subscription in $datum.Subscriptions) { Invoke-PSFProtectedCommand -Action 'Importing Subscription' -Target $subscription.Name -ScriptBlock { Register-NotificationSubscription @subscription -ErrorAction Stop } -PSCmdlet $PSCmdlet -EnableException $true } #region Loading additional Subscriptions if ($datum.SubscriptionPath) { $uri = $datum.SubscriptionPath -as [uri] if ($uri.IsAbsoluteUri) { $root = $datum.SubscriptionPath } else { $root = Join-Path -Path (Split-Path -Path $file) -ChildPath $datum.SubscriptionPath } Get-ChildItem -Path $root -Recurse | Where-Object Extension -In '.ps1', '.psd1' | ForEach-Object { $currentItem = $_ Write-PSFMessage -Message 'Importing subscriptions from: {0}' -StringValues $currentItem.FullName -Target $file try { if ($_.Extension -eq '.ps1') { $fileLoader.InvokeGlobal($_.FullName) return } $subscriptions = Import-PSFPowerShellDataFile -LiteralPath $currentItem.FullName -Psd1Mode Unsafe foreach ($subscription in $subscriptions) { Register-NotificationSubscription @subscription } } catch { Write-PSFMessage -Level Error -Message 'Failed to import subscriptions from: {0}' -StringValues $currentItem.FullName -ErrorRecord $_ $PSCmdlet.ThrowTerminatingError($_) } } } #endregion Loading additional Subscriptions #region Loading additional Providers if ($datum.ProviderPath) { $uri = $datum.ProviderPath -as [uri] if ($uri.IsAbsoluteUri) { $root = $datum.ProviderPath } else { $root = Join-Path -Path (Split-Path -Path $file) -ChildPath $datum.ProviderPath } Get-ChildItem -Path $root -Recurse | Where-Object Extension -In '.ps1' | ForEach-Object { $currentItem = $_ Write-PSFMessage -Message 'Importing providers from: {0}' -StringValues $currentItem.FullName -Target $file try { $fileLoader.InvokeGlobal($_.FullName) return } catch { Write-PSFMessage -Level Error -Message 'Failed to import providers from: {0}' -StringValues $currentItem.FullName -ErrorRecord $_ $PSCmdlet.ThrowTerminatingError($_) } } } #endregion Loading additional Providers } } } } function Invoke-NotificationWorkflow { <# .SYNOPSIS Executes a configured notification workflow agains the provided object. .DESCRIPTION Executes a configured notification workflow agains the provided object. This command expects items by pipeline - providing multiple objects without use of the pipeline will consider them all together as one entry. Either use "Import-NotificationWorkflow" first to load the notification configuration or provide the path to the notification via Parameter. See the documentation in "Import-NotificationWorkflow" for how to define workflow configuration files. .PARAMETER InputObject The item to notify over. .PARAMETER ID The ID of the workflow to execute. This ID is used to track when last an item was notified over, preventing spam. It must be available, but can also be provided via the configuration previously imported. .PARAMETER Path Path to the notification workflow configuration to import. Use in case you do not want to import the file as a separate step before calling this command. .EXAMPLE PS C:\> Get-ADUser -Filter * -Properties pwdlastSet | Invoke-NotificationWorkflow Send notifications for all users that match the previously configured workflows notification settings. This assumes you previously called "Import-NotificationWorkflow" to define what should be notified for (and how). .EXAMPLE PS C:\> Get-ADUser -Filter * -Properties pwdlastSet | Invoke-NotificationWorkflow -Path C:\monitoring\passwordexpiration\workflow.psd1 Send notifications for all users that match the workflows notification settings defined in "workflow.psd1". #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object] $InputObject, [string] $ID, [PSFFile] $Path ) begin { if ($Path) { Invoke-PSFProtectedCommand -Action 'Loading Workflow configuration from file' -Target ($Path -join ', ') -ScriptBlock { Import-NotificationWorkflow -Path $Path } -PSCmdlet $PSCmdlet -EnableException $true } if (-not $ID) { $ID = $script:_CurrentWorkflowID } if (-not $ID) { Stop-PSFFunction -Message 'No Workflow ID Provided, cannot notify!' -Cmdlet $PSCmdlet -EnableException $true -Category InvalidOperation } $subscriptions = Get-NotificationSubscription } process { Write-PSFMessage -Level SomewhatVerbose -Message 'Processing Item: {0}' -StringValues $InputObject -Target $InputObject -Tag start trap { Write-PSFMessage -Level Warning -Message 'Failed to process Item: {0}' -StringValues $InputObject -Target $InputObject -Tag fail return } #region Process Subscriptions foreach ($subscription in $subscriptions) { Write-PSFMessage -Level Debug -Message 'Start processing subscription {0} for {1}' -StringValues $subscription.Name, $InputObject -Target $InputObject -Tag subscription, start #region Validation # Test Condition try { $applies = $subscription.Condition.InvokeGlobal($InputObject) } catch { Write-PSFMessage -Level Error -Message 'Failed to validate condition on subscription {0} against {1}' -StringValues $subscription.Name, $InputObject -Tag fail, validate -Target $InputObject -ErrorRecord $_ -EnableException $true continue } if (-not $applies) { Write-PSFMessage -Level Debug -Message 'Subscription {0} does not apply to {1}' -StringValues $subscription.Name, $InputObject -Tag validate, skip -Target $InputObject continue } # Resolve Identity if ($subscription.Identity -is [string]) { $identityValue = $InputObject.$($subscription.Identity) } else { try { $identityValue = $subscription.Identity.InvokeGlobal($InputObject) -as [string] } catch { Write-PSFMessage -Level Error -Message 'Failed to resolve Identity for subscription {0} against {1}' -StringValues $subscription.Name, $InputObject -Tag fail, identity -Target $InputObject -ErrorRecord $_ -EnableException $true continue } } if (-not $identityValue) { Write-PSFMessage -Level Error -Message 'Failed to resolve Identity for subscription {0} against {1} | Resolved to NULL' -StringValues $subscription.Name, $InputObject -Tag fail, identity -Target $InputObject continue } $resolvedIdentityValue = $identityValue.ToBase64() # Resolve Provider & Execute Parameter resolution $providerObject = $script:_NotificationProviders[$subscription.Provider] if (-not $providerObject) { Write-PSFMessage -Level Error -Message 'Provider {0} specified in subscription {1} was not found!' -StringValues $subscription.Provider, $subscription.Name -Tag fail, provider -Target $InputObject continue } try { $parameters = $providerObject.Resolver.Resolve($InputObject, $false, $subscription.Parameters) } catch { Write-PSFMessage -Level Error -Message 'Failed to resolve Parameters for subscription {0} against {1} | Resolved to NULL' -StringValues $subscription.Name, $InputObject -Tag fail, parameters -Target $InputObject -ErrorRecord $_ -EnableException $true continue } #endregion Validation Send-Notification -Provider $ProviderObject -Subscription $subscription -Identity $resolvedIdentityValue -Parameters $parameters -WorkflowID $ID } #endregion Process Subscriptions } } function Send-Notification { <# .SYNOPSIS Sends out a notification as configured, tracking schedule and success. .DESCRIPTION Sends out a notification as configured, tracking schedule and success. This function assumes validation and parameter resolution _already have happened._ This is purely for executing the notification flow, bringing together subscription, provider and item. .PARAMETER Provider The provider object implementing the message execution. As returned by Get-NotificationProvider. .PARAMETER Subscription The subscription object triggering the notification. As returned by Get-NotificationSubscription. .PARAMETER Identity The Identity of the item being notified upon. Already resolved and converted into base64. Note: With this module loaded, all strings have the "ToBase64" method. .PARAMETER Parameters The fully resolved parameters to use when sending this notification. .PARAMETER WorkflowID The ID of the Workflow this is part of. Each notification must have an ID. .PARAMETER Force Force sending a notification, even though no message is due. By default, messages are only sent according to the configuration in the Subscription, to prevent spamming notifications. .EXAMPLE PS C:\> Send-Notification -Provider $ProviderObject -Subscription $subscription -Identity $resolvedIdentityValue -Parameters $parameters -WorkflowID $ID Sends the notification as configured. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [PSObject] $Provider, [Parameter(Mandatory = $true)] [PSObject] $Subscription, [Parameter(Mandatory = $true)] [string] $Identity, [Parameter(Mandatory = $true)] [hashtable] $Parameters, [Parameter(Mandatory = $true)] [string] $WorkflowID, [switch] $Force ) Begin { $rootPath = Get-PSFConfigValue -FullName 'PSFramework.Path.Notifier' $workflowPath = Join-Path -Path $rootPath -ChildPath $WorkflowID if (-not (Test-Path -Path $workflowPath)) { Invoke-PSFProtectedCommand -Action 'Creating Workflow Tracking Folder' -Target $WorkflowID -ScriptBlock { $null = New-Item -Path $workflowPath -ItemType Directory -Force -ErrorAction Stop } -PSCmdlet $PSCmdlet -EnableException $true } $msgCommon = @{ PSCmdlet = $PSCmdlet Target = $Identity } } process { $itemPath = Join-Path -Path $workflowPath -ChildPath $Identity try { $itemConfig = Import-PSFClixml -Path $itemPath -ErrorAction Stop } catch { $itemConfig = @{ Identity = $Identity LastAttempt = $null LastError = $null LastSuccess = $null } } if (-not $Force -and $Subscription.OnceOnly -and $itemConfig.LastSuccess) { Write-PSFMessage @msgCommon -Level Debug -Message "Notification already sent for {0} on subscription {1} to provider {2}. Skipping" -StringValues $Identity.FromBase64(), $Subscription.Name, $Provider.Name -Tag skip, send, notify return } if (-not $Force -and $Subscription.Interval -and $itemConfig.LastSuccess -and $itemConfig.LastSuccess.Add($Subscription.Interval) -gt (Get-Date)) { Write-PSFMessage @msgCommon -Level Debug -Message "Notification recently sent for {0} on subscription {1} to provider {2}. Skipping until {3:yyyy-MM-dd HH:mm}" -StringValues $Identity.FromBase64(), $Subscription.Name, $Provider.Name, $itemConfig.LastSuccess.Add($Subscription.Interval) -Tag skip, send, notify return } try { $itemConfig.LastAttempt = Get-Date & $Provider.ScriptBlock @Parameters $itemConfig.LastSuccess = Get-Date } catch { Write-PSFMessage @msgCommon -Level Warning -Message "Failed to write send notification for {0} on subscription {1} to provider {2}" -StringValues $Identity.FromBase64(), $Subscription.Name, $Provider.Name -Tag fail, send, notify -ErrorRecord $_ $itemConfig.LastError = $_ } try { $itemConfig | Export-PSFClixml -Path $itemPath -ErrorAction Stop } catch { Write-PSFMessage @msgCommon -Level Warning -Message "Failed to write status update for {0} on subscription {1} to provider {2}" -StringValues $Identity.FromBase64(), $Subscription.Name, $Provider.Name -Tag fail, export, status -ErrorRecord $_ -EnableException $true } } } function Get-NotificationProvider { <# .SYNOPSIS Lists registered notification providers. .DESCRIPTION Lists registered notification providers. Notification providers offer the implenting logic that performs the actual notification. For more details on how to define one, see the help on "Register-NotificationProvider" .PARAMETER Name The name of the provider to search for. Defaults to: * .EXAMPLE PS C:\> Get-NotificationProvider Lists all registered notification providers. #> [CmdletBinding()] param ( [string] $Name = '*' ) process { $script:_NotificationProviders.Values | Where-Object Name -Like $Name } } function Register-NotificationProvider { <# .SYNOPSIS Registers a new notification provider, implementing the logic to send notifications. .DESCRIPTION Registers a new notification provider, implementing the logic to send notifications. This is the part that enables sending notifications to email, eventlog, console, or wherever else. The code should include a param block defining all the input the provider needs. Providing parameter help will have that included in the finished provider for better user experience. Note: This provider does NOT receive the full object being notified over! Mapping item properties to parameters happens in the subscription. In other words, the provider should be fully agnostic over what is being notified about. .PARAMETER Name The name of the provider. .PARAMETER Description A description for the provider. Should tell a user what this provider is all about. .PARAMETER ScriptBlock The code implementing the provider. Should not be specific to the item being notified over. Should include parameters that give it all the information it needs. .EXAMPLE PS C:\> Register-NotificationProvider -Name Mail -Description 'A blank, direct, SMTP send' -ScriptBlock $code Registers an email-sending provider. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $Description, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) process { $script:_NotificationProviders[$Name] = [PSCustomObject]@{ PSTypeName = 'Notifier.Provider' Name = $Name Description = $Description Parameters = Get-Parameter -ScriptBlock $ScriptBlock -AsHashtable ScriptBlock = $ScriptBlock Resolver = $null } $script:_NotificationProviders[$Name].Resolver = [ParameterResolver]::new($script:_NotificationProviders[$Name].Parameters) } } function Get-NotificationSubscription { <# .SYNOPSIS Lists registered notification subscriptions. .DESCRIPTION Lists registered notification subscriptions. See "Register-NotificationSubscription" on how to define a subscription. .PARAMETER Name The name of the subscription to filter by. Defaults to: * .PARAMETER Provider The name of the provider the subscriptions use to filter by. Defaults to: * .EXAMPLE PS C:\> Get-NotificationSubscription Lists all registered notification subscriptions. #> [CmdletBinding()] param ( [string] $Name = '*', [string] $Provider = '*' ) process { $script:_NotificationSubscriptions.Values.Values | Where-Object { $_.Name -like $Name -and $_.Provider -like $Provider } } } function Register-NotificationSubscription { <# .SYNOPSIS Registers a subscription to get notified, when an applicable object is processed. .DESCRIPTION Registers a subscription to get notified, when an applicable object is processed. .PARAMETER Name Name of the subscription. Name must be unique within the subscription for this provider. .PARAMETER Provider Name of the provider doing the notifying. Providers implement the notification logic, such as sending emails, pushing message queues, etc. .PARAMETER Identity For each processed object, what is considered its Identity? Provide either ... - A Property Name as a string - A Scriptblock that returns a single string. In case of the former, that property from the input object will be used as Identity. If the property does not exist, a random GUID will be used. In case of the latter, that scriptblock will be executed with a single argument - the object. The Identity is used to apply notification interval settings, to prevent undesired spamming. .PARAMETER Description Describe your notification subscription. Allows including context of WHY you subscribed. .PARAMETER Parameters Parameters to provide to the Provider when sending a subscription. Use "Get-NotificationProvider" to check what parameters it requires. This hashtable maps parameter name to the value to assign to it, and there are three ways to provide values: - Plain, static values, that are the same for each notification triggered. - "%PropertyName%" - A string enclosed in % symbols uses a property from the object triggering the notification (without the '%') - A scriptblock will be dynamically executed for each object processed (unless the parameter on the provider expects a scriptblock, in which case it will be treated as a static value) Example: @{ Sender = 'monitoring@contoso.com' Recipient = '%UserMail%' Body = $mailBodyScript } .PARAMETER Condition A filter condition to apply to each object potentially notified upon. Equivalent to how you would filter using where-Object. .PARAMETER Interval How frequently we are willing to notify. This is tracked per identity and prevents spamming a target too frequently. .PARAMETER OnceOnly Whether an identity is notified once only, no matter how often it meets the condition. .PARAMETER Force Overwrite pre-existing subscriptions. By default, this command will refuse to accept a new subscription under a provider/name combination already registered. .EXAMPLE PS C:\> Register-NotificationSubscription -Provider mail -Name ExpiringUserCert -Identity Thumbprint -Parameters $param -Condition $certExpiring -Intervall '7.00:00:00' Registers the subscription "ExpiringUserCert" under the mail provider. It will not notify more frequently than once per week. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $Provider, [Parameter(Mandatory = $true)] $Identity, [string] $Description, [hashtable] $Parameters = @{ }, [Parameter(Mandatory = $true)] [scriptblock] $Condition, [timespan] $Interval, [switch] $OnceOnly, [switch] $Force ) begin { $killIt = $ErrorActionPreference -eq 'Stop' } process { if (-not $script:_NotificationSubscriptions[$Provider]) { $script:_NotificationSubscriptions[$Provider] = @{ } } if (-not $Force -and $script:_NotificationSubscriptions[$Provider][$Name]) { Stop-PSFFunction -Message "Notification Subscription exists already: $Provider > $Name" -EnableException $killIt -Cmdlet $PSCmdlet return } if ($Identity -is [scriptblock]) { $effectiveIdentity = [PsfScriptBlock]$Identity } elseif ($Identity -is [PsfScriptBlock]) { $effectiveIdentity = $Identity } else { $effectiveIdentity = $Identity -as [string] } $paramClone = $Parameters.Clone() foreach ($key in $($paramClone.Keys)) { if ($paramClone[$key] -isnot [scriptblock]) { continue } $paramClone[$key] = [PsfScriptBlock]$paramClone[$key] } $script:_NotificationSubscriptions[$Provider][$Name] = [PSCustomObject]@{ PSTypeName = 'Notifier.Subscription' Name = $Name Provider = $Provider Identity = $effectiveIdentity Description = $Description Parameters = $paramClone Condition = [PsfScriptblock]$Condition Interval = $Interval OnceOnly = $OnceOnly.ToBool() } } } Set-PSFConfig -FullName 'PSFramework.Path.Notifier' -Value (Join-Path -Path (Get-PSFPath -Name AppData) -ChildPath 'PowerShell/Notifier') -Initialize -Validation string -Description 'Path pointing at where the Notifier module stores its notification history. Used with Get-PSFPath' # Simplify String Operations if ("".PSObject.Methods.Name -notcontains 'ToBase64') { Update-TypeData -TypeName 'System.String' -MemberType ScriptMethod -MemberName ToBase64 -Value { [convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($this)) } } if ("".PSObject.Methods.Name -notcontains 'FromBase64') { Update-TypeData -TypeName 'System.String' -MemberType ScriptMethod -MemberName FromBase64 -Value { [System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String($this)) } } class ParameterResolver { [hashtable]$Parameters ParameterResolver([hashtable]$Parameters) { $this.Parameters = $Parameters } [hashtable]Resolve([object]$Data, [bool]$Force, [hashtable]$Parameters) { $newHash = $Parameters.Clone() foreach ($key in $($newHash.Keys)) { $value = $newHash[$key] if ($value -is [PsfScriptblock] -and $Parameters.$key.Type -notin 'scriptblock', 'psfscriptblock', 'System.Management.Automation.ScriptBlock', 'PSFramework.Utility.PsfScriptBlock') { $newHash[$key] = $value.InvokeEx($true, $Data, $Data, $null, $false, $false, @($Data)) continue } if ($value -notlike '%*%') { continue } $property = $value -replace '^%|%$' if ($Data.PSObject.Properties.Name -contains $property) { $newHash[$key] = $Data.$property } else { if ($Force) { continue } throw "Parameter not found on input: '$property' (Input: $Data)" } } # Validate Mandatory Parameters foreach ($parameter in $this.Parameters.Values) { if (-not $parameter.Mandatory) { continue } if ($newHash.Keys -contains $parameter.Name) { continue } throw "Mandatory Parameter $($parameter.Name) not specified!" } return $newHash } } # Providers used to send notifications $script:_NotificationProviders = @{ } # Subscriptions that govern what data entry triggers what kind of notification (and when) $script:_NotificationSubscriptions = @{ } # The ID of the current workflow # Workflows are used to persistently identify, track, and manage notification... workflows. # The module needs to track, what notifications were sent when, and how soon to resend them. # This information is tracked separately per Workflow, in order to not accidentally overlap. $script:_CurrentWorkflowID = $null Register-NotificationProvider -Name Mail -Description 'A blank, direct, SMTP send' -ScriptBlock { <# .SYNOPSIS A blank, direct, SMTP send. .DESCRIPTION A blank, direct, SMTP send. .PARAMETER From The From parameter is required. This parameter specifies the sender's email address. Enter a name (optional) and email address, such as `Name <someone@fabrikam.com>`. .PARAMETER To This parameter specifies the recipient's email address. Enter names (optional) and the email address, such as `Name <someone@fabrikam.com>`. .PARAMETER Subject This parameter specifies the subject of the email message. .PARAMETER Body Specifies the content of the email message. .PARAMETER SmtpServer Specifies the name of the SMTP server that sends the email message. .PARAMETER Credential Who to send as. Defaults to current user. .PARAMETER Cc Specifies the email addresses to which a carbon copy (CC) of the email message is sent. Enter names (optional) and the email address, such as `Name <someone@fabrikam.com>`. .PARAMETER Bcc Specifies the email addresses that receive a copy of the mail but aren't listed as recipients of the message. Enter names (optional) and the email address, such as `Name <someone@fabrikam.com>`. .PARAMETER BodyAsHtml Specifies that the value of the Body parameter contains HTML. .PARAMETER UseSsl The Secure Sockets Layer (SSL) protocol is used to establish a secure connection to the remote computer to send mail. By default, SSL isn't used. .PARAMETER Port Specifies an alternate port on the SMTP server. The default value is 25, which is the default SMTP port. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $From, [Parameter(Mandatory = $true)] [string[]] $To, [Parameter(Mandatory = $true)] [string] $Subject, [Parameter(Mandatory = $true)] [string] $Body, [Parameter(Mandatory = $true)] [string] $SmtpServer, [pscredential] $Credential, [string[]] $Cc, [string[]] $Bcc, [bool] $BodyAsHtml, [bool] $UseSsl, [int] $Port ) $param = $PSBoundParameters | ConvertTo-PSFHashtable Send-MailMessage @param -WarningAction SilentlyContinue } Register-NotificationProvider -Name MDMail -Description 'Send emails using the MailDaemon module. Install and configure module separately!' -ScriptBlock { <# .SYNOPSIS A superior way to send email, using the MailDaemon module. .DESCRIPTION A superior way to send email, using the MailDaemon module. .PARAMETER TaskName Name of the task that is sending the email. For ease of tracking in the logs. .PARAMETER From The email address of the sender. .PARAMETER To The email address to send to. .PARAMETER Subject The subject to send the email under. .PARAMETER Body The body of the email to send. .PARAMETER Cc Additional addresses to keep in the information flow. .PARAMETER Bcc Additional addresses to keep silently informed. .PARAMETER BodyAsHtml Whether the body is to be understood as html text. #> [CmdletBinding()] param ( [string] $TaskName = (New-Guid), [string] $From, [Parameter(Mandatory = $true)] [string[]] $To, [Parameter(Mandatory = $true)] [string] $Subject, [Parameter(Mandatory = $true)] [string] $Body, [string[]] $Cc, [string[]] $Bcc, [bool] $BodyAsHtml ) $param = $PSBoundParameters | ConvertTo-PSFHashtable -ReferenceCommand Set-MDMail Set-MDMail @param Send-MDMail -TaskName $TaskName } |