From 39ee34714126c1a2f86206718ba649b92af6664e Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 10 Feb 2026 08:43:57 +0100 Subject: [PATCH 1/7] 1.1.7 --- MailDaemon/MailDaemon.psd1 | 2 +- MailDaemon/changelog.md | 7 +++ MailDaemon/functions/Install-MDDaemon.ps1 | 51 ++++++++----------- MailDaemon/functions/Invoke-MDDaemon.ps1 | 1 + MailDaemon/functions/Set-MDDaemon.ps1 | 13 ++--- MailDaemon/internal/configurations/daemon.ps1 | 3 +- 6 files changed, 36 insertions(+), 41 deletions(-) diff --git a/MailDaemon/MailDaemon.psd1 b/MailDaemon/MailDaemon.psd1 index e1ef503..19d10fc 100644 --- a/MailDaemon/MailDaemon.psd1 +++ b/MailDaemon/MailDaemon.psd1 @@ -3,7 +3,7 @@ RootModule = 'MailDaemon.psm1' # Version number of this module. - ModuleVersion = '1.1.3' + ModuleVersion = '1.1.7' # ID used to uniquely identify this module GUID = 'd5ba333f-5210-4d69-83f0-150dd0909139' diff --git a/MailDaemon/changelog.md b/MailDaemon/changelog.md index 876d751..d0ce31e 100644 --- a/MailDaemon/changelog.md +++ b/MailDaemon/changelog.md @@ -1,5 +1,12 @@ # Changelog +## ??? + ++ Upd: Invoke-MDDaemon - implements `UseSSL` as configured ++ Upd: Set-MDDaemon - supports `-UseSSL` ++ Upd: Install-MDDaemon - supports `-UseSSL` ++ Fix: Install-MDDaemon - now exports credentials without prompting. + ## 1.1.3 (2024-11-11) + Upd: Added ability to directly embed attachments in the email task, rather than only providing a path to them. (thanks @jebbster88 ; #10) diff --git a/MailDaemon/functions/Install-MDDaemon.ps1 b/MailDaemon/functions/Install-MDDaemon.ps1 index 68a545b..aaf63a9 100644 --- a/MailDaemon/functions/Install-MDDaemon.ps1 +++ b/MailDaemon/functions/Install-MDDaemon.ps1 @@ -1,6 +1,5 @@ -function Install-MDDaemon -{ -<# +function Install-MDDaemon { + <# .SYNOPSIS Configures a computer for using the Mail Daemon @@ -103,11 +102,13 @@ $SenderCredential, [string] - $RecipientDefault + $RecipientDefault, + + [switch] + $UseSSL ) - begin - { + begin { #region Repetitions (ugly) # Specifying repetitions directly in the commandline is ugly. # It ignores explicit settings and requires copying the repetition object from another task. @@ -155,8 +156,7 @@ #endregion Repetitions (ugly) #region Setup Task Configuration - if (-not $NoTask) - { + if (-not $NoTask) { $action = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -Command Invoke-MDDaemon" $triggers = @() $triggers += New-ScheduledTaskTrigger -AtStartup -RandomDelay "00:15:00" @@ -173,8 +173,7 @@ TaskName = 'MailDaemon' InputObject = $taskItem } - if ($TaskUser) - { + if ($TaskUser) { $parametersRegister["User"] = $TaskUser.UserName $parametersRegister["Password"] = $TaskUser.GetNetworkCredential().Password } @@ -182,12 +181,7 @@ #endregion Setup Task Configuration #region Preparing Parameters - $parameters = @{ } - foreach ($key in $PSBoundParameters.Keys) - { - if ($key -notin 'PickupPath', 'SentPath', 'MailSentRetention', 'SmtpServer', 'SenderDefault', 'RecipientDefault') { continue } - $parameters[$key] = $PSBoundParameters[$key] - } + $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include 'PickupPath', 'SentPath', 'MailSentRetention', 'SmtpServer', 'SenderDefault', 'RecipientDefault', 'UseSSL' $paramMainInstallCall = @{ ArgumentList = $parameters @@ -217,8 +211,7 @@ #endregion The Main Setup Scriptblock } - process - { + process { #region Ensure Modules are installed $testResults = Test-Module -ComputerName $ComputerName -Credential $Credential -Module @{ MailDaemon = $script:ModuleVersion @@ -227,11 +220,9 @@ $failedTests = $testResults | Where-Object Success -EQ $false - if ($failedTests) - { + if ($failedTests) { $grouped = $failedTests | Group-Object Name - foreach ($groupSet in $grouped) - { + foreach ($groupSet in $grouped) { Copy-Module -ModuleName (Get-Module $groupSet.Name).ModuleBase -ToComputer $groupSet.Group.ComputerName } } @@ -242,13 +233,13 @@ Invoke-PSFCommand @paramMainInstallCall #region Securely store credentials - if ($PSBoundParameters.ContainsKey('SenderCredential')) - { + if ($PSBoundParameters.ContainsKey('SenderCredential')) { $parametersSave = @{ - ComputerName = $ComputerName - Credential = $SenderCredential - Path = 'C:\ProgramData\PowerShell\MailDaemon\senderCredentials.clixml' + ComputerName = $ComputerName + TargetCredential = $SenderCredential + Path = 'C:\ProgramData\PowerShell\MailDaemon\senderCredentials.clixml' } + if ($Credential) { $parametersSave['Credential'] = $Credential } if ($TaskUser) { $parametersSave['AccessAccount'] = $TaskUser } Save-MDCredential @parametersSave @@ -260,10 +251,8 @@ #endregion Securely store credentials #region Setup Task - if (-not $NoTask) - { - foreach ($computerObject in $ComputerName) - { + if (-not $NoTask) { + foreach ($computerObject in $ComputerName) { if ($ComputerName.Type -like 'CimSession') { $parametersRegister["CimSession"] = $computerObject.InputObject } elseif (-not $ComputerName.IsLocalhost) { $parametersRegister["CimSession"] = $ComputerName } diff --git a/MailDaemon/functions/Invoke-MDDaemon.ps1 b/MailDaemon/functions/Invoke-MDDaemon.ps1 index e626d3d..deae538 100644 --- a/MailDaemon/functions/Invoke-MDDaemon.ps1 +++ b/MailDaemon/functions/Invoke-MDDaemon.ps1 @@ -48,6 +48,7 @@ Encoding = ([System.Text.Encoding]::UTF8) ErrorAction = 'Stop' } + if (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.UseSSL' -Fallback $false) { $parameters['UseSSL'] = $true } if ($email.To) { $parameters["To"] = $email.To } else { $parameters["To"] = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.RecipientDefault' } if ($email.From) { $parameters["From"] = $email.From } diff --git a/MailDaemon/functions/Set-MDDaemon.ps1 b/MailDaemon/functions/Set-MDDaemon.ps1 index 49cbf19..36a0993 100644 --- a/MailDaemon/functions/Set-MDDaemon.ps1 +++ b/MailDaemon/functions/Set-MDDaemon.ps1 @@ -66,6 +66,9 @@ [string] $SenderCredentialPath, + + [switch] + $UseSSL, [Parameter(ValueFromPipeline = $true)] [PSFComputer[]] @@ -106,6 +109,7 @@ 'SenderDefault' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.SenderDefault' -Value $Parameters[$key] } 'SenderCredentialPath' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.SenderCredentialPath' -Value $Parameters[$key] } 'RecipientDefault' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.RecipientDefault' -Value $Parameters[$key] } + 'UseSSL' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.UseSSL' -Value $Parameters[$key].ToBool() } } } @@ -113,14 +117,7 @@ } #endregion Configuration Script - #region Prepare parameters to pass through - $parameters = @{ } - foreach ($key in $PSBoundParameters.Keys) - { - if ($key -in 'ComputerName', 'Credential') { continue } - $parameters[$key] = $PSBoundParameters[$key] - } - #endregion Prepare parameters to pass through + $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Exclude ComputerName, Credential } process { diff --git a/MailDaemon/internal/configurations/daemon.ps1 b/MailDaemon/internal/configurations/daemon.ps1 index beb28ee..f1c7bb8 100644 --- a/MailDaemon/internal/configurations/daemon.ps1 +++ b/MailDaemon/internal/configurations/daemon.ps1 @@ -4,4 +4,5 @@ Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailSentRetention' -Value (New- Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.SmtpServer' -Value "mail.$($env:USERDNSDOMAIN)" -Initialize -Validation 'string' -SimpleExport -Description "The mail server to use." Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.SenderDefault' -Value "maildaemon@$($env:USERDNSDOMAIN)" -Initialize -Validation 'string' -SimpleExport -Description "The default sending email address." Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.SenderCredentialPath' -Value '' -Initialize -Validation 'string' -SimpleExport -Description "The path to the credentials to use for authenticated mail sending." -Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.RecipientDefault' -Value "support@$($env:USERDNSDOMAIN)" -Initialize -Validation 'string' -SimpleExport -Description "The default recipient to receive emails." \ No newline at end of file +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.RecipientDefault' -Value "support@$($env:USERDNSDOMAIN)" -Initialize -Validation 'string' -SimpleExport -Description "The default recipient to receive emails." +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.UseSSL' -Value $false -Initialize -Validation 'bool' -SimpleExport -Description "Whether mails should be sent using SSL." \ No newline at end of file From b8aedf78e44a53c1a23b9c4be2a00a80523ef93b Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 10 Feb 2026 08:44:49 +0100 Subject: [PATCH 2/7] Update MailDaemon.psd1 --- MailDaemon/MailDaemon.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailDaemon/MailDaemon.psd1 b/MailDaemon/MailDaemon.psd1 index 19d10fc..9993441 100644 --- a/MailDaemon/MailDaemon.psd1 +++ b/MailDaemon/MailDaemon.psd1 @@ -21,7 +21,7 @@ Description = 'Mail Daemon as PowerShell Module' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' + PowerShellVersion = '5.1' # Modules that must be imported into the global environment prior to importing # this module From 9987acfb09e88d6f836ea2005122aad9804d6268 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 10 Feb 2026 08:45:32 +0100 Subject: [PATCH 3/7] Update changelog.md --- MailDaemon/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailDaemon/changelog.md b/MailDaemon/changelog.md index d0ce31e..a56d2ee 100644 --- a/MailDaemon/changelog.md +++ b/MailDaemon/changelog.md @@ -1,6 +1,6 @@ # Changelog -## ??? +## 1.1.7 (2026-02-10) + Upd: Invoke-MDDaemon - implements `UseSSL` as configured + Upd: Set-MDDaemon - supports `-UseSSL` From 9468e097ec37a7cdc60e2260616a2fc584a1fd6e Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 10 Feb 2026 08:55:20 +0100 Subject: [PATCH 4/7] fixing help --- MailDaemon/functions/Install-MDDaemon.ps1 | 3 +++ MailDaemon/functions/Set-MDDaemon.ps1 | 3 +++ 2 files changed, 6 insertions(+) diff --git a/MailDaemon/functions/Install-MDDaemon.ps1 b/MailDaemon/functions/Install-MDDaemon.ps1 index aaf63a9..c628a05 100644 --- a/MailDaemon/functions/Install-MDDaemon.ps1 +++ b/MailDaemon/functions/Install-MDDaemon.ps1 @@ -56,6 +56,9 @@ .PARAMETER RecipientDefault Default email address to send the email to, if the individual script queuing the email does not specify one. + .PARAMETER UseSSL + Use SSL for sending emails. + .EXAMPLE PS C:\> Install-MDDaemon -ComputerName DC1, DC2, DC3 -TaskUser $cred -DaemonUser "DOMAIN\MailDaemon" -SmtpServer 'mail.domain.org' -SenderDefault 'daemon@domain.org' -RecipientDefault 'helpdesk-t2@domain.org' diff --git a/MailDaemon/functions/Set-MDDaemon.ps1 b/MailDaemon/functions/Set-MDDaemon.ps1 index 36a0993..a1f4915 100644 --- a/MailDaemon/functions/Set-MDDaemon.ps1 +++ b/MailDaemon/functions/Set-MDDaemon.ps1 @@ -28,6 +28,9 @@ .PARAMETER SenderCredentialPath The path to where the credentials file can be found, that should be used by the daemon. + + .PARAMETER UseSSL + Use SSL for sending emails. .PARAMETER ComputerName The computer(s) to work against. From ec37d481da0fb3bfd15e7344681be8ec930805e6 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Wed, 11 Feb 2026 12:31:56 +0100 Subject: [PATCH 5/7] handling failed emails --- MailDaemon/MailDaemon.psd1 | 4 +- MailDaemon/changelog.md | 11 +- MailDaemon/en-us/strings.psd1 | 31 ++-- MailDaemon/functions/Install-MDDaemon.ps1 | 58 ++++++-- MailDaemon/functions/Invoke-MDDaemon.ps1 | 136 +++++++++++------- MailDaemon/functions/Send-MDMail.ps1 | 4 +- MailDaemon/functions/Set-MDDaemon.ps1 | 50 ++++--- MailDaemon/internal/configurations/daemon.ps1 | 9 +- 8 files changed, 204 insertions(+), 99 deletions(-) diff --git a/MailDaemon/MailDaemon.psd1 b/MailDaemon/MailDaemon.psd1 index 9993441..19095a4 100644 --- a/MailDaemon/MailDaemon.psd1 +++ b/MailDaemon/MailDaemon.psd1 @@ -3,7 +3,7 @@ RootModule = 'MailDaemon.psm1' # Version number of this module. - ModuleVersion = '1.1.7' + ModuleVersion = '1.2.13' # ID used to uniquely identify this module GUID = 'd5ba333f-5210-4d69-83f0-150dd0909139' @@ -26,7 +26,7 @@ # Modules that must be imported into the global environment prior to importing # this module RequiredModules = @( - @{ ModuleName='PSFramework'; ModuleVersion='1.12.346' } + @{ ModuleName='PSFramework'; ModuleVersion='1.13.416' } ) # Assemblies that must be loaded prior to importing this module diff --git a/MailDaemon/changelog.md b/MailDaemon/changelog.md index a56d2ee..3c75c44 100644 --- a/MailDaemon/changelog.md +++ b/MailDaemon/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 1.2.13 (2026-02-11) + ++ New: Emails that could not be sent will no longer be permanently attempted to resend - after 14 days ++ Upd: Install-MDDaemon - now supports remote-deployment of the daemon task using credentials ++ Upd: Install-MDDaemon - now installs eventlog for MailDaemon ++ Upd: Invoke-MDDaemon - adds logging to the Windows Eventlog by default ++ Fix: Install-MDDaemon - no longer fails when the daemon task already exists ++ Fix: Invoke-MDDaemon - fails to use authentication for SMTP + ## 1.1.7 (2026-02-10) + Upd: Invoke-MDDaemon - implements `UseSSL` as configured @@ -25,7 +34,7 @@ ## 0.1.1 (2019-02-09) + Fix: Bug in Install-MDDaemon causing errors during installation - + ## 0.1.0 (2019-02-09) + New: Everything diff --git a/MailDaemon/en-us/strings.psd1 b/MailDaemon/en-us/strings.psd1 index d0b107a..c868cf7 100644 --- a/MailDaemon/en-us/strings.psd1 +++ b/MailDaemon/en-us/strings.psd1 @@ -2,29 +2,30 @@ # Write-PSFMessage, Stop-PSFFunction or the PSFramework validation scriptblocks @{ # General - 'General.ModuleMissing' = 'The MailDaemon module could not be found in sufficient version on: {0}. Terminating Execution. To install or update to the current version, use Install-MDDaemon or access the PSGallery directly using "Install-Module MailDaemon".' + 'General.ModuleMissing' = 'The MailDaemon module could not be found in sufficient version on: {0}. Terminating Execution. To install or update to the current version, use Install-MDDaemon or access the PSGallery directly using "Install-Module MailDaemon".' # Invoke-MDDaemon - 'Invoke-MDDaemon.SendMail.Start' = '{0} - Sending Mail: "{1}" From {2} to {3}' - 'Invoke-MDDaemon.SendMail.Failed' = '{0} - Failed to send email!' - 'Invoke-MDDaemon.SendMail.Success' = '{0} - Email sent successfully' - 'Invoke-MDDaemon.ManageSuccessJob.Failed' = '{0} - Failed to move mail task to the "sent" folder!' + 'Invoke-MDDaemon.SendMail.Abandon' = '{0} - Abandoning email after failing to send it within the configured timespan ({1})' # $email.Taskname, $abandonThreshold + 'Invoke-MDDaemon.SendMail.Start' = '{0} - Sending Mail: "{1}" From {2} to {3}' + 'Invoke-MDDaemon.SendMail.Failed' = '{0} - Failed to send email!' + 'Invoke-MDDaemon.SendMail.Success' = '{0} - Email sent successfully' + 'Invoke-MDDaemon.ManageSuccessJob.Failed' = '{0} - Failed to move mail task to the "sent" folder!' # Copy-Module - 'Copy-Module.ReceivingModule' = 'Receiving Module from {0}: {1}' - 'Copy-Module.ReceivingModule.Failed' = 'Failed to receive Module from {0}: {1}' - 'Copy-Module.InstallingModule' = 'Installing module {0} on: {1}' + 'Copy-Module.ReceivingModule' = 'Receiving Module from {0}: {1}' + 'Copy-Module.ReceivingModule.Failed' = 'Failed to receive Module from {0}: {1}' + 'Copy-Module.InstallingModule' = 'Installing module {0} on: {1}' # Send-MDMail - 'Send-MDMail.Folder.CreationFailed' = 'Failed to create outgoing mail folder: {0}' - 'Send-MDMail.Email.NotRegisteredYet' = 'No email has been set up yet!' - 'Send-MDMail.Email.Sending' = 'Queueing email for sending: {0}' - 'Send-MDMail.Email.SendingFailed' = 'Failed to queue email for sending: {0}' - 'Send-MDMail.Email.TriggerFailed' = 'Failed to trigger the Mail Daemon task to send {0}' + 'Send-MDMail.Folder.CreationFailed' = 'Failed to create outgoing mail folder: {0}' + 'Send-MDMail.Email.NotRegisteredYet' = 'No email has been set up yet!' + 'Send-MDMail.Email.Sending' = 'Queueing email for sending: {0}' + 'Send-MDMail.Email.SendingFailed' = 'Failed to queue email for sending: {0}' + 'Send-MDMail.Email.TriggerFailed' = 'Failed to trigger the Mail Daemon task to send {0}' # Set-MDDaemon - 'Set-MDDaemon.UpdatingSettings' = 'Starting Daemon configuration update on {0}' - 'Set-MDDaemon.UpdateSetting' = 'Updating the Daemon configuration setting {0} to {1}' + 'Set-MDDaemon.UpdatingSettings' = 'Starting Daemon configuration update on {0}' + 'Set-MDDaemon.UpdateSetting' = 'Updating the Daemon configuration setting {0} to {1}' # Update-MDFolderPermission 'Update-MDFolderPermission.Granting.DaemonUser' = 'Assigning write permissions as daemon account to {0} on "{1}" and "{2}"' diff --git a/MailDaemon/functions/Install-MDDaemon.ps1 b/MailDaemon/functions/Install-MDDaemon.ps1 index c628a05..d70762d 100644 --- a/MailDaemon/functions/Install-MDDaemon.ps1 +++ b/MailDaemon/functions/Install-MDDaemon.ps1 @@ -30,6 +30,9 @@ .PARAMETER SentPath The folder in which emails that were successfully sent are stored for a specified time before being deleted. + + .PARAMETER FailedPath + The path where mails that could repeatedly not be sent are moved to. .PARAMETER DaemonUser The user to grant permissions needed to function as the Daemon account. @@ -41,6 +44,12 @@ .PARAMETER MailSentRetention The time to keep successfully sent emails around. + + .PARAMETER MailAbandonThreshold + How long we attempt to send an email before abandoning it and moving it to -FailedPath. + + .PARAMETER MailFailedRetention + How long we keep an abandoned email around before removing it entirely. .PARAMETER SmtpServer The mailserver to use for sending emails. @@ -59,6 +68,10 @@ .PARAMETER UseSSL Use SSL for sending emails. + .PARAMETER NoLogging + Disables logging. + Unless specified, this setup step will also prepare the windows eventlog by creating a dedicated eventlog for MailDaemon. + .EXAMPLE PS C:\> Install-MDDaemon -ComputerName DC1, DC2, DC3 -TaskUser $cred -DaemonUser "DOMAIN\MailDaemon" -SmtpServer 'mail.domain.org' -SenderDefault 'daemon@domain.org' -RecipientDefault 'helpdesk-t2@domain.org' @@ -85,6 +98,9 @@ [string] $SentPath, + + [string] + $FailedPath, [string] $DaemonUser, @@ -94,6 +110,12 @@ [Timespan] $MailSentRetention, + + [Timespan] + $MailAbandonThreshold, + + [Timespan] + $MailFailedRetention, [string] $SmtpServer, @@ -108,7 +130,10 @@ $RecipientDefault, [switch] - $UseSSL + $UseSSL, + + [switch] + $NoLogging ) begin { @@ -161,6 +186,7 @@ #region Setup Task Configuration if (-not $NoTask) { $action = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -Command Invoke-MDDaemon" + if ($NoLogging) { $action = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -Command Invoke-MDDaemon -NoLogging" } $triggers = @() $triggers += New-ScheduledTaskTrigger -AtStartup -RandomDelay "00:15:00" $triggers += New-ScheduledTaskTrigger -At "00:00:00" -Daily @@ -184,7 +210,7 @@ #endregion Setup Task Configuration #region Preparing Parameters - $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include 'PickupPath', 'SentPath', 'MailSentRetention', 'SmtpServer', 'SenderDefault', 'RecipientDefault', 'UseSSL' + $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include 'PickupPath', 'SentPath', 'FailedPath', 'MailSentRetention', 'MailAbandonThreshold', 'MailFailedRetention', 'SmtpServer', 'SenderDefault', 'RecipientDefault', 'UseSSL' $paramMainInstallCall = @{ ArgumentList = $parameters @@ -246,21 +272,35 @@ if ($TaskUser) { $parametersSave['AccessAccount'] = $TaskUser } Save-MDCredential @parametersSave - $parametersInvoke = @{ $parametersInvoke['ComputerName'] = $ComputerName } + $parametersInvoke = @{ ComputerName = $ComputerName } + if ($Credential) { $parametersInvoke['Credential'] = $Credential } Invoke-PSFCommand @parametersInvoke -ScriptBlock { Set-MDDaemon -SenderCredentialPath "C:\ProgramData\PowerShell\MailDaemon\senderCredentials.clixml" } } #endregion Securely store credentials + + #region Setup Logging + if (-not $NoLogging) { + Invoke-PSFCommand @parametersInvoke -ScriptBlock { + if ($PSVersionTable.PSVersion.Major -gt 5 -and -not $IsWindows) { return } + Set-PSFLoggingProvider -Name eventlog -InstanceName MailDaemonInvoke -LogName MailDaemon -Source MailDaemon -Enabled $true -Wait + Write-PSFMessage -Message "Setting up MailDaemon logging" + Disable-PSFLoggingProvider -Name eventlog -InstanceName MailDaemonInvoke + } + } + #endregion Setup Logging #region Setup Task if (-not $NoTask) { - foreach ($computerObject in $ComputerName) { - if ($ComputerName.Type -like 'CimSession') { $parametersRegister["CimSession"] = $computerObject.InputObject } - elseif (-not $ComputerName.IsLocalhost) { $parametersRegister["CimSession"] = $ComputerName } - - $null = Register-ScheduledTask @parametersRegister - } + Invoke-PSFCommand @parametersInvoke -ScriptBlock { + param ($ParametersRegister) + + $taskObject = Get-ScheduledTask -TaskName $ParametersRegister.TaskName -ErrorAction Ignore + if ($taskObject) { $taskObject | Unregister-ScheduledTask } + + $null = Register-ScheduledTask @ParametersRegister + } -ArgumentList $parametersRegister } #endregion Setup Task } diff --git a/MailDaemon/functions/Invoke-MDDaemon.ps1 b/MailDaemon/functions/Invoke-MDDaemon.ps1 index deae538..fa67264 100644 --- a/MailDaemon/functions/Invoke-MDDaemon.ps1 +++ b/MailDaemon/functions/Invoke-MDDaemon.ps1 @@ -1,5 +1,4 @@ -function Invoke-MDDaemon -{ +function Invoke-MDDaemon { <# .SYNOPSIS Processes the email queue and sends emails @@ -12,40 +11,54 @@ - Launch on Midnight - Repeat every 30 minutes for one day + .PARAMETER NoLogging + Disables Eventlog logging. + By default, the mail invocation is logged to the Windows Eventlog. + .EXAMPLE PS C:\> Invoke-MDDaemon Processes the email queue and sends emails #> [CmdletBinding()] - Param ( - + param ( + [switch] + $NoLogging ) - begin - { - if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath'))) - { + begin { + if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath'))) { $null = New-Item -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') -ItemType Directory } - if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath'))) - { + if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath'))) { $null = New-Item -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath') -ItemType Directory } + $failedPath = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailFailedPath' + $abandonThreshold = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailAbandonThreshold' + if (-not (Test-Path $failedPath)) { + $null = New-Item -Path $failedPath -ItemType Directory + } + + if (-not $NoLogging -and ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows)) { + Set-PSFLoggingProvider -Name eventlog -InstanceName MailDaemonInvoke -LogName MailDaemon -Source MailDaemon -Enabled $true -Wait + } } - process - { + process { + trap { + Disable-PSFLoggingProvider -Name eventlog -InstanceName MailDaemonInvoke + throw $_ + } + #region Send mails - foreach ($item in (Get-ChildItem -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') -Filter "*.clixml")) - { + foreach ($item in (Get-ChildItem -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') -Filter "*.clixml")) { $email = Import-Clixml -Path $item.FullName # Skip emails that should not yet be processed if ($email.NotBefore -gt (Get-Date)) { continue } # Build email parameters $parameters = @{ - SmtpServer = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.SmtpServer' - Encoding = ([System.Text.Encoding]::UTF8) + SmtpServer = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.SmtpServer' + Encoding = ([System.Text.Encoding]::UTF8) ErrorAction = 'Stop' } if (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.UseSSL' -Fallback $false) { $parameters['UseSSL'] = $true } @@ -57,63 +70,84 @@ if ($email.Bcc) { $parameters["Bcc"] = $email.Bcc } if ($email.Subject) { $parameters["Subject"] = $email.Subject } else { $parameters["Subject"] = "" } - if ($email.Priority) {$parameters["Priority"] = $email.Priority} + if ($email.Priority) { $parameters["Priority"] = $email.Priority } if ($email.Body) { $parameters["Body"] = $email.Body } if ($null -ne $email.BodyAsHtml) { $parameters["BodyAsHtml"] = $email.BodyAsHtml } if ($email.Attachments) { - if ($email.AttachmentsBinary) { - $tempAttachmentParentDir = New-Item (join-path $item.Directory $item.BaseName) -Force -ItemType Directory - $attachmentCounter = 0 - $parameters["Attachments"] = @() - # Using multiple subfolders to allow for duplicate attachment names - foreach ($binaryAttachment in $email.AttachmentsBinary) { - $tempAttachmentDir = new-item (join-path $tempAttachmentParentDir $attachmentCounter) -Force -ItemType Directory - $tempAttachmentPath = join-path $tempAttachmentDir $binaryAttachment.Name - $null = [System.IO.File]::WriteAllBytes($tempAttachmentPath, $binaryAttachment.Data) - $parameters["Attachments"] = @($parameters["Attachments"]) + $tempAttachmentPath - $attachmentCounter = $attachmentCounter + 1 - } - } else { - $parameters["Attachments"] = $email.Attachments - } - } - if ($script:_Config.SenderCredentialPath) { $parameters["Credential"] = Import-Clixml (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.SenderCredentialPath') } + if ($email.AttachmentsBinary) { + $tempAttachmentParentDir = New-Item (Join-Path $item.Directory $item.BaseName) -Force -ItemType Directory + $attachmentCounter = 0 + $parameters["Attachments"] = @() + # Using multiple subfolders to allow for duplicate attachment names + foreach ($binaryAttachment in $email.AttachmentsBinary) { + $tempAttachmentDir = New-Item (Join-Path $tempAttachmentParentDir $attachmentCounter) -Force -ItemType Directory + $tempAttachmentPath = Join-Path $tempAttachmentDir $binaryAttachment.Name + $null = [System.IO.File]::WriteAllBytes($tempAttachmentPath, $binaryAttachment.Data) + $parameters["Attachments"] = @($parameters["Attachments"]) + $tempAttachmentPath + $attachmentCounter = $attachmentCounter + 1 + } + } + else { + $parameters["Attachments"] = $email.Attachments + } + } + if (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.SenderCredentialPath') { $parameters["Credential"] = Import-Clixml -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.SenderCredentialPath') } Write-PSFMessage -Level Verbose -String 'Invoke-MDDaemon.SendMail.Start' -StringValues @($email.Taskname, $parameters['Subject'], $parameters['From'], ($parameters['To'] -join ",")) -Target $email.Taskname try { Send-MailMessage @parameters } - catch { Stop-PSFFunction -String 'Invoke-MDDaemon.SendMail.Failed' -StringValues $email.Taskname -ErrorRecord $_ -Continue -Target $email.Taskname } + catch { + "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') : $_" | Set-PSFFileContent -Path ($item.FullName -replace '.clixml', '.txt') -Append + #region Abandon Email if beyond threshold + if ($item.CreationTime.Add($abandonThreshold) -lt (Get-Date)) { + Write-PSFMessage 'Invoke-MDDaemon.SendMail.Abandon' -StringValues $email.Taskname, $abandonThreshold + $item.LastWriteTime = Get-Date + Move-Item -LiteralPath $item.FullName -Destination $failedPath + Move-Item -LiteralPath ($item.FullName -replace '.clixml', '.txt') -Destination $failedPath + if ($email.Attachments -and $email.RemoveAttachments) { + foreach ($attachment in $email.Attachments) { + Remove-Item $attachment -Force + } + } + } + #endregion Abandon Email if beyond threshold + Stop-PSFFunction -String 'Invoke-MDDaemon.SendMail.Failed' -StringValues $email.Taskname -ErrorRecord $_ -Continue -Target $email.Taskname + } Write-PSFMessage -Level Verbose -String 'Invoke-MDDaemon.SendMail.Success' -StringValues $email.Taskname -Target $email.Taskname - # Remove attachments only if ordered and maail was sent successfully - if ($email.Attachments -and $email.RemoveAttachments) - { - foreach ($attachment in $email.Attachments) - { + # Remove attachments only if ordered and mail was sent successfully + if ($email.Attachments -and $email.RemoveAttachments) { + foreach ($attachment in $email.Attachments) { Remove-Item $attachment -Force } } # Remove temp deserialized attachments if used - if ($email.AttachmentsBinary) { - $null = remove-item -Path $tempAttachmentParentDir -Recurse -Force - } + if ($email.AttachmentsBinary) { + $null = Remove-Item -Path $tempAttachmentParentDir -Recurse -Force + } # Update the timestamp (the timeout for deletion uses this) and move it to the sent items folder $item.LastWriteTime = Get-Date try { Move-Item -Path $item.FullName -Destination (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath') -Force -ErrorAction Stop } - catch - { + catch { Write-PSFMessage -Level Warning -String 'Invoke-MDDaemon.ManageSuccessJob.Failed' -StringValues $email.Taskname -Target $email.Taskname } } #endregion Send mails + + Disable-PSFLoggingProvider -Name eventlog -InstanceName MailDaemonInvoke } - end - { + end { #region Cleanup expired mails - foreach ($item in (Get-ChildItem -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath'))) - { - if ($item.LastWriteTime -lt (Get-Date).AddTicks((-1 * (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentRetention').Ticks))) - { + $sentRetention = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentRetention' + foreach ($item in (Get-ChildItem -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath'))) { + if ($item.LastWriteTime.Add($sentRetention) -lt (Get-Date)) { + Remove-Item $item.FullName + } + } + + $failedRetention = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailFailedRetention' + foreach ($item in (Get-ChildItem -Path $failedPath)) { + if ($item.LastWriteTime.Add($failedRetention) -lt (Get-Date)) { Remove-Item $item.FullName } } diff --git a/MailDaemon/functions/Send-MDMail.ps1 b/MailDaemon/functions/Send-MDMail.ps1 index d17aac8..42905f0 100644 --- a/MailDaemon/functions/Send-MDMail.ps1 +++ b/MailDaemon/functions/Send-MDMail.ps1 @@ -24,7 +24,9 @@ [Parameter(Mandatory = $true)] [string] $TaskName, - [switch]$PersistAttachments + + [switch] + $PersistAttachments ) begin diff --git a/MailDaemon/functions/Set-MDDaemon.ps1 b/MailDaemon/functions/Set-MDDaemon.ps1 index a1f4915..bf7d18a 100644 --- a/MailDaemon/functions/Set-MDDaemon.ps1 +++ b/MailDaemon/functions/Set-MDDaemon.ps1 @@ -1,6 +1,5 @@ -function Set-MDDaemon -{ -<# +function Set-MDDaemon { + <# .SYNOPSIS Configures the Daemon settings on the target computer(s) @@ -12,9 +11,18 @@ .PARAMETER SentPath The folder in which emails that were successfully sent are stored for a specified time before being deleted. + + .PARAMETER FailedPath + The path where mails that could repeatedly not be sent are moved to. .PARAMETER MailSentRetention The time to keep successfully sent emails around. + + .PARAMETER MailAbandonThreshold + How long we attempt to send an email before abandoning it and moving it to -FailedPath. + + .PARAMETER MailFailedRetention + How long we keep an abandoned email around before removing it entirely. .PARAMETER SmtpServer The mailserver to use for sending emails. @@ -54,9 +62,18 @@ [string] $SentPath, + + [string] + $FailedPath, [Timespan] $MailSentRetention, + + [Timespan] + $MailAbandonThreshold, + + [Timespan] + $MailFailedRetention, [string] $SmtpServer, @@ -81,8 +98,7 @@ $Credential ) - begin - { + begin { #region Configuration Script $configurationScript = { param ( @@ -92,22 +108,24 @@ # Import module so settings are initialized if (-not (Get-Module MailDaemon)) { Import-Module MailDaemon } - foreach ($key in $Parameters.Keys) - { + foreach ($key in $Parameters.Keys) { Write-PSFMessage -String 'Set-MDDaemon.UpdateSetting' -StringValues $key, $Parameters[$key] - switch ($key) - { - 'PickupPath' - { + switch ($key) { + 'PickupPath' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailPickupPath' -Value $Parameters[$key] if (-not (Test-Path $Parameters[$key])) { $null = New-Item $Parameters[$key] -Force -ItemType Directory } } - 'SentPath' - { + 'SentPath' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailSentPath' -Value $Parameters[$key] if (-not (Test-Path $Parameters[$key])) { $null = New-Item $Parameters[$key] -Force -ItemType Directory } } + 'FailedPath' { + Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailFailedPath' -Value $Parameters[$key] + if (-not (Test-Path $Parameters[$key])) { $null = New-Item $Parameters[$key] -Force -ItemType Directory } + } 'MailSentRetention' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailSentRetention' -Value $Parameters[$key] } + 'MailAbandonThreshold' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailAbandonThreshold' -Value $Parameters[$key] } + 'MailFailedRetention' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailFailedRetention' -Value $Parameters[$key] } 'SmtpServer' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.SmtpServer' -Value $Parameters[$key] } 'SenderDefault' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.SenderDefault' -Value $Parameters[$key] } 'SenderCredentialPath' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.SenderCredentialPath' -Value $Parameters[$key] } @@ -122,14 +140,12 @@ $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Exclude ComputerName, Credential } - process - { + process { #region Modules must be installed and current if ($moduleResult = Test-Module -ComputerName $ComputerName -Credential $Credential -Module @{ MailDaemon = $script:ModuleVersion PSFramework = (Get-Module -Name PSFramework).Version - } | Where-Object Success -EQ $false) - { + } | Where-Object Success -EQ $false) { Stop-PSFFunction -String 'General.ModuleMissing' -StringValues ($moduleResult.ComputerName -join ", ") -EnableException $true -Cmdlet $PSCmdlet } #endregion Modules must be installed and current diff --git a/MailDaemon/internal/configurations/daemon.ps1 b/MailDaemon/internal/configurations/daemon.ps1 index f1c7bb8..f651622 100644 --- a/MailDaemon/internal/configurations/daemon.ps1 +++ b/MailDaemon/internal/configurations/daemon.ps1 @@ -1,6 +1,9 @@ -Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailPickupPath' -Value "$($env:ProgramData)\PowerShell\MailDaemon\Pickup" -Initialize -Validation 'string' -SimpleExport -Description "The folder from which the daemon will pickup email tasks." -Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailSentPath' -Value "$($env:ProgramData)\PowerShell\MailDaemon\Sent" -Initialize -Validation 'string' -SimpleExport -Description "The folder into which completed tasks are moved" -Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailSentRetention' -Value (New-TimeSpan -Days 7) -Initialize -Validation 'timespan' -SimpleExport -Description "How long sent email tasks are retented" +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailPickupPath' -Value "$(Get-PSFPath -Name ProgramData)\PowerShell\MailDaemon\Pickup" -Initialize -Validation 'string' -SimpleExport -Description "The folder from which the daemon will pickup email tasks." +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailSentPath' -Value "$(Get-PSFPath -Name ProgramData)\PowerShell\MailDaemon\Sent" -Initialize -Validation 'string' -SimpleExport -Description "The folder into which completed tasks are moved" +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailFailedPath' -Value "$(Get-PSFPath -Name ProgramData)\PowerShell\MailDaemon\Sent" -Initialize -Validation 'string' -SimpleExport -Description "The folder into which failed tasks are moved" +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailSentRetention' -Value (New-TimeSpan -Days 7) -Initialize -Validation 'timespan' -SimpleExport -Description "How long sent email tasks are retained" +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailAbandonThreshold' -Value (New-TimeSpan -Days 14) -Initialize -Validation 'timespan' -SimpleExport -Description "How long we try to send failing tasks, before abandoning them" +Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.MailFailedRetention' -Value (New-TimeSpan -Days 14) -Initialize -Validation 'timespan' -SimpleExport -Description "How long failed email tasks are retained" Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.SmtpServer' -Value "mail.$($env:USERDNSDOMAIN)" -Initialize -Validation 'string' -SimpleExport -Description "The mail server to use." Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.SenderDefault' -Value "maildaemon@$($env:USERDNSDOMAIN)" -Initialize -Validation 'string' -SimpleExport -Description "The default sending email address." Set-PSFConfig -Module 'MailDaemon' -Name 'Daemon.SenderCredentialPath' -Value '' -Initialize -Validation 'string' -SimpleExport -Description "The path to the credentials to use for authenticated mail sending." From 0c0c610504a7b4623f02f39521a71f58c15636b5 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Wed, 11 Feb 2026 12:50:55 +0100 Subject: [PATCH 6/7] fixing string usage --- MailDaemon/functions/Invoke-MDDaemon.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailDaemon/functions/Invoke-MDDaemon.ps1 b/MailDaemon/functions/Invoke-MDDaemon.ps1 index fa67264..a2d4a09 100644 --- a/MailDaemon/functions/Invoke-MDDaemon.ps1 +++ b/MailDaemon/functions/Invoke-MDDaemon.ps1 @@ -99,7 +99,7 @@ "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') : $_" | Set-PSFFileContent -Path ($item.FullName -replace '.clixml', '.txt') -Append #region Abandon Email if beyond threshold if ($item.CreationTime.Add($abandonThreshold) -lt (Get-Date)) { - Write-PSFMessage 'Invoke-MDDaemon.SendMail.Abandon' -StringValues $email.Taskname, $abandonThreshold + Write-PSFMessage -String 'Invoke-MDDaemon.SendMail.Abandon' -StringValues $email.Taskname, $abandonThreshold $item.LastWriteTime = Get-Date Move-Item -LiteralPath $item.FullName -Destination $failedPath Move-Item -LiteralPath ($item.FullName -replace '.clixml', '.txt') -Destination $failedPath From a88e318b3dca536e17c7d6e6e18bd31305008f4f Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Thu, 12 Feb 2026 08:40:12 +0100 Subject: [PATCH 7/7] fixing permissions --- MailDaemon/MailDaemon.psd1 | 2 +- MailDaemon/changelog.md | 4 ++++ MailDaemon/functions/Update-MDFolderPermission.ps1 | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/MailDaemon/MailDaemon.psd1 b/MailDaemon/MailDaemon.psd1 index 19095a4..595cfb1 100644 --- a/MailDaemon/MailDaemon.psd1 +++ b/MailDaemon/MailDaemon.psd1 @@ -3,7 +3,7 @@ RootModule = 'MailDaemon.psm1' # Version number of this module. - ModuleVersion = '1.2.13' + ModuleVersion = '1.2.14' # ID used to uniquely identify this module GUID = 'd5ba333f-5210-4d69-83f0-150dd0909139' diff --git a/MailDaemon/changelog.md b/MailDaemon/changelog.md index 3c75c44..300af93 100644 --- a/MailDaemon/changelog.md +++ b/MailDaemon/changelog.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.14 (2026-02-12) + ++ Fix: Update-MDFolderPermission - does not set permissions for the failed folder + ## 1.2.13 (2026-02-11) + New: Emails that could not be sent will no longer be permanently attempted to resend - after 14 days diff --git a/MailDaemon/functions/Update-MDFolderPermission.ps1 b/MailDaemon/functions/Update-MDFolderPermission.ps1 index f873a11..b89dfe8 100644 --- a/MailDaemon/functions/Update-MDFolderPermission.ps1 +++ b/MailDaemon/functions/Update-MDFolderPermission.ps1 @@ -62,8 +62,9 @@ Import-Module MailDaemon - $pickupPath = (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') - $sentPath = (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath') + $pickupPath = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath' + $sentPath = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath' + $failedPath = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailFailedPath' if ($DaemonUser.Trim()) { @@ -76,6 +77,9 @@ $acl = Get-Acl -Path $sentPath $acl.AddAccessRule($rule) $acl | Set-Acl -Path $sentPath + $acl = Get-Acl -Path $failedPath + $acl.AddAccessRule($rule) + $acl | Set-Acl -Path $failedPath } foreach ($user in $WriteUser) {