Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions MailDaemon/MailDaemon.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
RootModule = 'MailDaemon.psm1'

# Version number of this module.
ModuleVersion = '1.3.19'
ModuleVersion = '1.3.32'

# ID used to uniquely identify this module
GUID = 'd5ba333f-5210-4d69-83f0-150dd0909139'
Expand All @@ -26,7 +26,7 @@
# Modules that must be imported into the global environment prior to importing
# this module
RequiredModules = @(
@{ ModuleName='PSFramework'; ModuleVersion='1.13.416' }
@{ ModuleName='PSFramework'; ModuleVersion='1.14.457' }
@{ ModuleName='EntraAuth'; ModuleVersion='1.8.52' }
)

Expand Down
12 changes: 12 additions & 0 deletions MailDaemon/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 1.3.32 (2026-07-14)

+ Upd: Install-MDDaemon - added `-UsePWSH` parameter to allow running mailer with PowerShell 7
+ Fix: Install-MDDaemon - fails to register scheduled task when not providing sender credentials
+ Fix: Install-MDDaemon - incorrectly detects modules as installed on local setup, when the modules are only installed for the local user
+ Fix: Install-MDDaemon - fails to ensure proper installation of EntraAuth on targeted computer
+ Fix: Install-MDDaemon - fails to ensure proper directory access for specified daemon & mail senders.
+ Fix: Invoke-MDDaemon - fails to clear temp folder if sending mails with binary attachment-data attached
+ Fix: Update-MDFolderPermission - fails to apply proper mail sender permission.
+ Fix: Set-MDMail - The `-Priority` parameter ignores the "Normal" value.
+ Fix: Config - Failed email tasks were put into the same folder as successfully sent emails.

## 1.3.19 (2026-05-28)

+ Major: Added capability to send emails by Graph API, rather than SMTP.
Expand Down
4 changes: 2 additions & 2 deletions MailDaemon/en-us/strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
'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}' # ($ComputerName -join ", ")
'Set-MDDaemon.UpdateSetting' = 'Updating the Daemon configuration setting {0} to {1}' # $key, $Parameters[$key]

# Update-MDFolderPermission
'Update-MDFolderPermission.Granting.DaemonUser' = 'Assigning write permissions as daemon account to {0} on "{1}" and "{2}"'
Expand Down
24 changes: 17 additions & 7 deletions MailDaemon/functions/Install-MDDaemon.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
Disables logging.
Unless specified, this setup step will also prepare the windows eventlog by creating a dedicated eventlog for MailDaemon.

.PARAMETER UsePWSH
When setting up the mail daemon task, use PowerShell 7, rather than the default Windows PowerShell

.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'

Expand Down Expand Up @@ -172,7 +175,10 @@
$CertificateName,

[switch]
$NoLogging
$NoLogging,

[switch]
$UsePWSH
)

begin {
Expand Down Expand Up @@ -224,8 +230,10 @@

#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" }
$executable = 'powershell.exe'
if ($UsePWSH) { $executable = 'pwsh.exe' }
$action = New-ScheduledTaskAction -Execute $executable -Argument "-NoProfile -Command Invoke-MDDaemon"
if ($NoLogging) { $action = New-ScheduledTaskAction -Execute $executable -Argument "-NoProfile -Command Invoke-MDDaemon -NoLogging" }
$triggers = @()
$triggers += New-ScheduledTaskTrigger -AtStartup -RandomDelay "00:15:00"
$triggers += New-ScheduledTaskTrigger -At "00:00:00" -Daily
Expand All @@ -249,7 +257,7 @@
#endregion Setup Task Configuration

#region Preparing Parameters
$parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include 'PickupPath', 'SentPath', 'FailedPath', 'MailSentRetention', 'MailAbandonThreshold', 'MailFailedRetention', 'SmtpServer', 'SenderDefault', 'RecipientDefault', 'UseSSL', 'ClientID', 'TenantID', 'Identity', 'Federated', 'CertificateThumbprint', 'CertificateName'
$parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include 'PickupPath', 'SentPath', 'FailedPath', 'MailSentRetention', 'MailAbandonThreshold', 'MailFailedRetention', 'SmtpServer', 'SenderDefault', 'RecipientDefault', 'UseSSL', 'ClientID', 'TenantID', 'Identity', 'Federated', 'CertificateThumbprint', 'CertificateName', 'DaemonUser','WriteUser'
if ($parameters.Federated -or $parameters.Identity -or $parameters.ClientID) { $parameters.Type = 'Graph' }

$paramMainInstallCall = @{
Expand Down Expand Up @@ -285,7 +293,8 @@
$testResults = Test-Module -ComputerName $ComputerName -Credential $Credential -Module @{
MailDaemon = $script:ModuleVersion
PSFramework = (Get-Module -Name PSFramework).Version
}
EntraAuth = (Get-Module -Name EntraAuth).Version
} -Scope AllUsers

$failedTests = $testResults | Where-Object Success -EQ $false

Expand All @@ -300,6 +309,9 @@
$paramMainInstallCall['ComputerName'] = $ComputerName

Invoke-PSFCommand @paramMainInstallCall

$parametersInvoke = @{ ComputerName = $ComputerName }
if ($Credential) { $parametersInvoke['Credential'] = $Credential }

#region Securely store credentials
if ($PSBoundParameters.ContainsKey('SenderCredential')) {
Expand All @@ -312,8 +324,6 @@
if ($TaskUser) { $parametersSave['AccessAccount'] = $TaskUser }
Save-MDCredential @parametersSave

$parametersInvoke = @{ ComputerName = $ComputerName }
if ($Credential) { $parametersInvoke['Credential'] = $Credential }
Invoke-PSFCommand @parametersInvoke -ScriptBlock {
Set-MDDaemon -SenderCredentialPath "C:\ProgramData\PowerShell\MailDaemon\senderCredentials.clixml"
}
Expand Down
5 changes: 5 additions & 0 deletions MailDaemon/functions/Invoke-MDDaemon.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@
}
}
}
# Remove temp deserialized attachments if used
if ($email.AttachmentsBinary) {
$null = Remove-Item -Path $tempAttachmentParentDir -Recurse -Force
}

#endregion Abandon Email if beyond threshold
Stop-PSFFunction -String 'Invoke-MDDaemon.SendMail.Failed' -StringValues $email.Taskname -ErrorRecord $_ -Continue -Target $email.Taskname
}
Expand Down
2 changes: 1 addition & 1 deletion MailDaemon/functions/Set-MDMail.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,6 @@ function Set-MDMail
if ($Attachments) { $script:mail["Attachments"] = $Attachments }
if ($RemoveAttachments.IsPresent) { $script:mail["RemoveAttachments"] = ([bool]$RemoveAttachments) }
if ($NotBefore) { $script:mail["NotBefore"] = $NotBefore }
if ($Priority) { $script:mail["Priority"] = $Priority }
if ($PSBoundParameters.Keys -contains 'Priority') { $script:mail["Priority"] = $Priority }
}
}
2 changes: 1 addition & 1 deletion MailDaemon/functions/Update-MDFolderPermission.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
}
foreach ($user in $WriteUser)
{
if ($user.Trim()) { continue }
if (-not $user.Trim()) { continue }
Write-PSFMessage -String 'Update-MDFolderPermission.Granting.WriteUser' -StringValues $user, $pickupPath
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($user, 'Write', 'Allow')

Expand Down
2 changes: 1 addition & 1 deletion MailDaemon/internal/configurations/daemon.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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.MailFailedPath' -Value "$(Get-PSFPath -Name ProgramData)\PowerShell\MailDaemon\Failed" -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"
Expand Down
2 changes: 1 addition & 1 deletion MailDaemon/internal/functions/Copy-Module.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
#region Specified a module name
else
{
$moduleObject = Get-Module $Module | Sort-Object Version -Descending | Select-Object -First 1
$moduleObject = Get-Module $Module -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1
if (-not $moduleObject)
{
return [pscustomobject]@{
Expand Down
69 changes: 40 additions & 29 deletions MailDaemon/internal/functions/Test-Module.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
function Test-Module
{
<#
function Test-Module {
<#
.SYNOPSIS
Tests for module existence.

Expand Down Expand Up @@ -31,6 +30,9 @@

.PARAMETER Quiet
Disables output objects and instead returns $true if all modules specified meet the requirements, $false if not so.

.PARAMETER Scope
What scope the module has been installed to.

.PARAMETER ComputerName
The computers on which to test.
Expand Down Expand Up @@ -81,6 +83,10 @@

[switch]
$Quiet,

[ValidateSet('AllUsers','CurrentUser','Any')]
[string]
$Scope = 'Any',

[Parameter(ValueFromPipeline = $true)]
[PSFComputer[]]
Expand All @@ -91,16 +97,13 @@
$Credential
)

begin
{
begin {
#region Prepare Module parameter
$moduleHash = $Module
foreach ($moduleName in $Name)
{
foreach ($moduleName in $Name) {
$moduleHash[$moduleName] = $Version
}
foreach ($key in ([string[]]$moduleHash.Keys))
{
foreach ($key in ([string[]]$moduleHash.Keys)) {
$moduleHash[$key] = $moduleHash[$key] -as [Version]
if (-not $moduleHash[$key]) { $moduleHash[$key] = ([Version]'0.0.0.0') }
}
Expand All @@ -116,12 +119,14 @@
$Test,

[bool]
$Quiet
$Quiet,

[string]
$Scope
)

#region Utility Functions
function Write-Result
{
function Write-Result {
[CmdletBinding()]
param (
[string]
Expand All @@ -140,32 +145,40 @@
$result = [bool]$Success

[PSCustomObject]@{
Name = $Name
Success = $result
Name = $Name
Success = $result
VersionsFound = $VersionsFound
ComputerName = $env:COMPUTERNAME
Test = $Test
Test = $Test
}
}
#endregion Utility Functions

$userPath = $HOME

#region Validate each module specified
foreach ($module in $ModuleHash.Keys)
{
$modulesFound = Get-Module -Name $module -ListAvailable
foreach ($module in $ModuleHash.Keys) {
$modulesFound = Get-Module -Name $module -ListAvailable | Where-Object {
$Scope -eq 'Any' -or
(
$Scope -eq 'CurrentUser' -and
$_.ModuleBase -like "$($userPath)*"
) -or
(
$Scope -eq 'AllUsers' -and
$_.ModuleBase -notlike "$($userPath)*"
)
}
if ($Quiet -and (-not $modulesFound)) { return $false }

if ($ModuleHash[$module] -le '0.0.0.0')
{
if ($ModuleHash[$module] -le '0.0.0.0') {
Write-Result -Name $module -Success $modulesFound -VersionsFound $modulesFound.Version -Test $Test
continue
}

#region Quiet Validation [Calls Continue]
if ($Quiet)
{
switch ($Test)
{
if ($Quiet) {
switch ($Test) {
'LesserThan' { if (-not ($modulesFound | Where-Object Version -LT $ModuleHash[$module])) { return $false } }
'LesserEqual' { if (-not ($modulesFound | Where-Object Version -LE $ModuleHash[$module])) { return $false } }
'Equal' { if (-not ($modulesFound | Where-Object Version -EQ $ModuleHash[$module])) { return $false } }
Expand All @@ -176,8 +189,7 @@
}
#endregion Quiet Validation [Calls Continue]

switch ($Test)
{
switch ($Test) {
'LesserThan' { Write-Result -Name $module -Success ($modulesFound | Where-Object Version -LT $ModuleHash[$module]) -VersionsFound $modulesFound.Version -Test $Test }
'LesserEqual' { Write-Result -Name $module -Success ($modulesFound | Where-Object Version -LE $ModuleHash[$module]) -VersionsFound $modulesFound.Version -Test $Test }
'Equal' { Write-Result -Name $module -Success ($modulesFound | Where-Object Version -EQ $ModuleHash[$module]) -VersionsFound $modulesFound.Version -Test $Test }
Expand All @@ -191,8 +203,7 @@
}
#endregion Validation Scriptblock
}
process
{
Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ArgumentList $moduleHash, $Test, $Quiet.ToBool() -HideComputerName
process {
Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ArgumentList $moduleHash, $Test, $Quiet.ToBool(), $Scope -HideComputerName
}
}