MailDaemon.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\MailDaemon.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName MailDaemon.Import.DoDotSource -Fallback $false
if ($MailDaemon_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName MailDaemon.Import.IndividualFiles -Fallback $false
if ($MailDaemon_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }
    
function Import-ModuleFile
{
    <#
        .SYNOPSIS
            Loads files into the module on module import.
         
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
             
            This provides a central location to react to files being imported, if later desired
         
        .PARAMETER Path
            The path to the file to load
         
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
     
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )
    
    if ($doDotSource) { . (Resolve-Path $Path) }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText((Resolve-Path $Path)))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles)
{
    # Execute Preimport actions
    . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1"
    
    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Execute Postimport actions
    . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1"
    
    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
<#
This file loads the strings documents from the respective language folders.
This allows localizing messages and errors.
Load psd1 language files for each language you wish to support.
Partial translations are acceptable - when missing a current language message,
it will fallback to English or another available language.
#>

Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'MailDaemon' -Language 'en-US'

function Copy-Module
{
<#
    .SYNOPSIS
        Copies a module from one computer to another.
     
    .DESCRIPTION
        Copies a module from one computer to another.
        All transfers done via WinRM / Powershell Remoting.
     
    .PARAMETER ModuleName
        The name of the module to copy.
        Also accepts a path to the module root folder.
     
    .PARAMETER ModuleObject
        A specific module instance to copy (returned by Get-Module).
     
    .PARAMETER FromComputer
        The computer from which to pick up the module.
        Localhost by default.
        Accepts and reuses PSSession objects.
     
    .PARAMETER ToComputer
        The computer(s) on which to install the module.
        Accepts and reuses PSSession objects.
     
    .PARAMETER Credential
        The credentials to use when connecting to computers.
     
    .EXAMPLE
        PS C:\> Copy-Module -ModuleName BeerFactory -ToComputer server1
     
        Copies the module 'BeerFactory' from localhost to server1
#>

    
    [CmdletBinding(DefaultParameterSetName = 'Object')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'String', Position = 0)]
        [string[]]
        $ModuleName,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Object')]
        [PSModuleInfo[]]
        $ModuleObject,
        
        [Parameter(Mandatory = $true)]
        [PSFComputer[]]
        $ToComputer,
        
        [PSFComputer]
        $FromComputer = $env:COMPUTERNAME,
        
        [PSCredential]
        $Credential
    )
    
    begin
    {
        $receiveScript = {
            param (
                [string]
                $Module
            )
            
            #region Specified a path
            $uri = [uri]$Module
            if ($uri.IsFile)
            {
                if (-not (Test-Path $Module))
                {
                    return [pscustomobject]@{
                        Module  = $Module
                        Success = $false
                        Data    = @()
                    }
                }
                
                $sourcePath = "$($Module)\*"
                $moduleName = (Get-Module $Module -ListAvailable).Name
                $moduleVersion = (Get-Module $Module -ListAvailable).Version
            }
            #endregion Specified a path
            #region Specified a module name
            else
            {
                $moduleObject = Get-Module $Module | Sort-Object Version -Descending | Select-Object -First 1
                if (-not $moduleObject)
                {
                    return [pscustomobject]@{
                        Module = $Module
                        Success = $false
                        Data = @()
                    }
                }
                
                $sourcePath = "$($moduleObject.ModuleBase)\*"
                $moduleName = $moduleObject.Name
                $moduleVersion = $moduleObject.Version
            }
            #endregion Specified a module name
            
            #region Gather module object
            $tempPath = "$($env:TEMP)\$(New-Guid).zip"
            $workingFolder = New-Item -Path $env:TEMP -Name (New-Guid) -ItemType Directory
            # Copy item is important, as the zip commands cannot access locked dlls, copy-item can.
            Copy-Item -Path $sourcePath -Destination $workingFolder.FullName -Recurse
            Compress-Archive -Path "$($workingFolder.FullName)\*" -DestinationPath $tempPath
            [pscustomobject]@{
                Name    = $moduleName
                Version = $moduleVersion
                Data    = [System.IO.File]::ReadAllBytes($tempPath)
                Module  = $Module
                Success = $true
            }
            Remove-Item $tempPath
            Remove-Item $workingFolder.FullName -Recurse -Force
            #endregion Gather module object
        }
        
        $installScript = {
            param (
                $Modules
            )
            
            #region Update the modules
            foreach ($module in $Modules)
            {
                $installRoot = "$($env:ProgramFiles)\WindowsPowerShell\Modules"
                if (-not (Test-Path "$($installRoot)\$($module.Name)")) { $null = New-Item -Path $installRoot -Name $module.Name -ItemType Directory -Force }
                $root = New-Item -Path "$($installRoot)\$($module.Name)" -Name $module.Version -ItemType Directory -Force
                $tempPath = "$($env:TEMP)\$(New-Guid).zip"
                [System.IO.File]::WriteAllBytes($tempPath, $Module.Data)
                Expand-Archive -Path $tempPath -DestinationPath $root.FullName -Force
                Remove-Item $tempPath
            }
            #endregion Update the modules
        }
    }
    process
    {
        foreach ($name in $ModuleName)
        {
            Write-PSFMessage -String 'Copy-Module.ReceivingModule' -StringValues $FromComputer, $name
            $module = Invoke-PSFCommand -ComputerName $FromComputer -Credential $Credential -ScriptBlock $receiveScript -ArgumentList $name
            if (-not $module.Success)
            {
                Stop-PSFFunction -String 'Copy-Module.ReceivingModule.Failed' -StringValues $FromComputer, $name -Continue -SilentlyContinue -Cmdlet $PSCmdlet
            }
            Write-PSFMessage -String 'Copy-Module.InstallingModule' -StringValues $name, ($ToComputer -join ", ")
            Invoke-PSFCommand -ComputerName $ToComputer -Credential $Credential -ScriptBlock $installScript -ArgumentList $module
        }
        foreach ($object in $ModuleObject)
        {
            Write-PSFMessage -String 'Copy-Module.ReceivingModule' -StringValues $FromComputer, $object.ModuleBase
            $module = Invoke-PSFCommand -ComputerName $FromComputer -Credential $Credential -ScriptBlock $receiveScript -ArgumentList $object.ModuleBase
            if (-not $module.Success)
            {
                Stop-PSFFunction -String 'Copy-Module.ReceivingModule.Failed' -StringValues $FromComputer, $object.ModuleBase -Continue -SilentlyContinue -Cmdlet $PSCmdlet
            }
            Write-PSFMessage -String 'Copy-Module.InstallingModule' -StringValues $object.ModuleBase, ($ToComputer -join ", ")
            Invoke-PSFCommand -ComputerName $ToComputer -Credential $Credential -ScriptBlock $installScript -ArgumentList $module
        }
    }
}

function Test-Module
{
<#
    .SYNOPSIS
        Tests for module existence.
     
    .DESCRIPTION
        Tests whether a module - or set of modules - exists on the target machine(s).
        Includes support for version requirements (minimum or maximum).
     
    .PARAMETER Name
        Name of the module(s) to search for.
     
    .PARAMETER Version
        The version constraint.
        Whether that is the minimum, maximum or exactly this version is governed by the -Test parameter.
        The same version constraint will be applied to all modules specified!
        For custom versions per module, please use the -Module parameter to specify a hashtable with the mapping.
     
    .PARAMETER Module
        The combination of modules and versions to test.
        Specify the modulename as key and the version as value.
        E.g.: @{ MailDaemon = '1.0.0' }
        Specify '0.0' in order to not test about any specific version.
     
    .PARAMETER Test
        How to test for version.
        By default, the test will search for 'GreaterEqual' (that is: At least the specified version).
        Supported scenarios: 'LesserThan', 'LesserEqual', 'Equal', 'GreaterEqual', 'GreaterThan'
        Note on Lesser* comparisons: This only tests whether a version below the limit is present. It does not Test that NO greater version is available!
     
    .PARAMETER Quiet
        Disables output objects and instead returns $true if all modules specified meet the requirements, $false if not so.
     
    .PARAMETER ComputerName
        The computers on which to test.
        Uses WinRM / PowerShell Remoting to perform test.
     
    .PARAMETER Credential
        The credentials to use for connecting to computers for the test.
        Will be ignored for localhost.
     
    .EXAMPLE
        PS C:\> Test-Module -Name 'MyModule'
     
        Tests whether the module MyModule is available in any version.
     
    .EXAMPLE
        PS C:\> Test-Module -Name MailDaemon -Version 1.1.0 -ComputerName 'server1', 'Server2'
     
        Tests whether the module MailDaemon is available in at least version 1.1.0 on the computers server1 and server2.
     
    .EXAMPLE
        PS C:\> Test-Module -Name PSFramework -Version 1.0.0 -Quiet -Test 'Equal'
     
        Returns $true if the module PSFramework exists locally in exactly version 1.0.0, $false otherwise.
     
    .EXAMPLE
        PS C:\> Test-Module -Module @{ PSFramework = '1.0.0'; MailDaemon = '1.1.0' } -Test 'LesserThan'
     
        Returns whether PSFramework is present in any version less than 1.0.0
        Returns whether MailDaemon is present in any version less than 1.1.0
#>

    [CmdletBinding(DefaultParameterSetName = 'Name')]
    param (
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Name')]
        [string[]]
        $Name,
        
        [Parameter(Position = 1, ParameterSetName = 'Name')]
        [version]
        $Version = '0.0.0.0',
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Hash')]
        [hashtable]
        $Module = @{ },
        
        [ValidateSet('LesserThan', 'LesserEqual', 'Equal', 'GreaterEqual', 'GreaterThan')]
        [string]
        $Test = 'GreaterEqual',
        
        [switch]
        $Quiet,
        
        [Parameter(ValueFromPipeline = $true)]
        [PSFComputer[]]
        $ComputerName = $env:COMPUTERNAME,
        
        [AllowNull()]
        [PSCredential]
        $Credential
    )
    
    begin
    {
        #region Prepare Module parameter
        $moduleHash = $Module
        foreach ($moduleName in $Name)
        {
            $moduleHash[$moduleName] = $Version
        }
        foreach ($key in ([string[]]$moduleHash.Keys))
        {
            $moduleHash[$key] = $moduleHash[$key] -as [Version]
            if (-not $moduleHash[$key]) { $moduleHash[$key] = ([Version]'0.0.0.0') }
        }
        #endregion Prepare Module parameter
        
        #region Validation Scriptblock
        $scriptBlock = {
            param (
                [hashtable]
                $ModuleHash,
                
                [string]
                $Test,
                
                [bool]
                $Quiet
            )
            
            #region Utility Functions
            function Write-Result
            {
                [CmdletBinding()]
                param (
                    [string]
                    $Name,
                    
                    $Success,
                    
                    [AllowNull()]
                    [AllowEmptyCollection()]
                    $VersionsFound,
                    
                    [string]
                    $Test
                )
                
                $result = [bool]$Success
                
                [PSCustomObject]@{
                    Name          = $Name
                    Success          = $result
                    VersionsFound = $VersionsFound
                    ComputerName  = $env:COMPUTERNAME
                    Test          = $Test
                }
            }
            #endregion Utility Functions
            
            #region Validate each module specified
            foreach ($module in $ModuleHash.Keys)
            {
                $modulesFound = Get-Module -Name $module -ListAvailable
                if ($Quiet -and (-not $modulesFound)) { return $false }
                
                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)
                    {
                        '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 } }
                        'GreaterEqual' { if (-not ($modulesFound | Where-Object Version -GE $ModuleHash[$module])) { return $false } }
                        'GreaterThan' { if (-not ($modulesFound | Where-Object Version -GT $ModuleHash[$module])) { return $false } }
                    }
                    continue
                }
                #endregion Quiet Validation [Calls Continue]
                
                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 }
                    'GreaterEqual' { Write-Result -Name $module -Success ($modulesFound | Where-Object Version -GE $ModuleHash[$module]) -VersionsFound $modulesFound.Version -Test $Test }
                    'GreaterThan' { Write-Result -Name $module -Success ($modulesFound | Where-Object Version -GT $ModuleHash[$module]) -VersionsFound $modulesFound.Version -Test $Test }
                }
            }
            #endregion Validate each module specified
            
            if ($Quiet) { return $true }
        }
        #endregion Validation Scriptblock
    }
    process
    {
        Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ArgumentList $moduleHash, $Test, $Quiet.ToBool() -HideComputerName
    }
}

function Add-MDMailContent
{
    <#
        .SYNOPSIS
            Adds content to a pending email.
 
        .DESCRIPTION
            Adds content to a pending email.
            Use this command to incrementally add to the mail sent.
 
        .PARAMETER Body
            Add text to the mail body.
 
        .PARAMETER Attachments
            Add files to the list of files to send.
 
        .EXAMPLE
            PS C:\> Add-MDMailContent -Body "Phase 3: Completed"
 
            Adds the line "Phase 3: Completed" to the email body.
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Body,

        [string[]]
        $Attachments
    )
    
    begin
    {
        if (-not $script:mail)
        {
            $script:mail = @{ }
        }
    }
    process
    {
        if ($Body)
        {
            if (-not ($script:mail["Body"])) { $script:mail["body"] = $Body }
            else { $script:mail["Body"] = $script:mail["Body"], $Body -join "`n" }
        }
    }
}


function Install-MDDaemon
{
<#
    .SYNOPSIS
        Configures a computer for using the Mail Daemon
     
    .DESCRIPTION
        Configures a computer for using the Mail Daemon.
        This can include:
        - Installing the scheduled task
        - Creating folder and permission structure
        - Setting up the mail daemon configuration
         
        This action can be performed both locally or against remote computers
     
    .PARAMETER ComputerName
        The computer(s) to work against.
        Defaults to localhost, but can be used to install the module and set up the task across a wide range of computers.
     
    .PARAMETER Credential
        The credentials to use when connecting to computers.
     
    .PARAMETER NoTask
        Create the scheduled task.
     
    .PARAMETER TaskUser
        The credentials of the user the scheduled task will be executed as.
     
    .PARAMETER PickupPath
        The folder in which emails are queued for delivery.
     
    .PARAMETER SentPath
        The folder in which emails that were successfully sent are stored for a specified time before being deleted.
     
    .PARAMETER DaemonUser
        The user to grant permissions needed to function as the Daemon account.
        This grants read/write access to all working folders.
     
    .PARAMETER WriteUser
        The user/group to grant permissions to needed to queue email.
        This grants write-only access to the mail inbox.
     
    .PARAMETER MailSentRetention
        The time to keep successfully sent emails around.
     
    .PARAMETER SmtpServer
        The mailserver to use for sending emails.
     
    .PARAMETER SenderDefault
        The default email address to use as sender.
        This is used for mails queued by a task that did not specify a sender.
     
    .PARAMETER SenderCredential
        The credentials to use to send emails.
        Will be stored in an encrypted file that can only be opened by the taskuser and from the computer it is installed on.
     
    .PARAMETER RecipientDefault
        Default email address to send the email to, if the individual script queuing the email does not specify one.
     
    .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'
         
        Configures the mail daemon NoTask on the servers DC1, DC2 and DC3
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [PSFComputer[]]
        $ComputerName = $env:COMPUTERNAME,
        
        [PSCredential]
        $Credential,
        
        [switch]
        $NoTask,
        
        [PSCredential]
        $TaskUser,
        
        [string]
        $PickupPath,
        
        [string]
        $SentPath,
        
        [string]
        $DaemonUser,
        
        [string[]]
        $WriteUser,
        
        [Timespan]
        $MailSentRetention,
        
        [string]
        $SmtpServer,
        
        [string]
        $SenderDefault,
        
        [PSCredential]
        $SenderCredential,
        
        [string]
        $RecipientDefault
    )
    
    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.
        # Since we do not want to rely on another task being available, instead I chose to store an object in its XML form.
        # By deserializing this back into an object at runtime we can carry an object in scriptcode.
        $object = @'
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
    <TN RefId="0">
    <T>Microsoft.Management.Infrastructure.CimInstance#Root/Microsoft/Windows/TaskScheduler/MSFT_TaskRepetitionPattern</T>
    <T>Microsoft.Management.Infrastructure.CimInstance#MSFT_TaskRepetitionPattern</T>
    <T>Microsoft.Management.Infrastructure.CimInstance</T>
    <T>System.Object</T>
    </TN>
    <ToString>MSFT_TaskRepetitionPattern</ToString>
    <Props>
    <S N="Duration">P1D</S>
    <S N="Interval">PT30M</S>
    <B N="StopAtDurationEnd">false</B>
    <Nil N="PSComputerName" />
    </Props>
    <MS>
    <Obj N="__ClassMetadata" RefId="1">
        <TN RefId="1">
        <T>System.Collections.ArrayList</T>
        <T>System.Object</T>
        </TN>
        <LST>
        <Obj RefId="2">
            <MS>
            <S N="ClassName">MSFT_TaskRepetitionPattern</S>
            <S N="Namespace">Root/Microsoft/Windows/TaskScheduler</S>
            <S N="ServerName">C0020127</S>
            <I32 N="Hash">-1401671928</I32>
            <S N="MiXml">&lt;CLASS NAME="MSFT_TaskRepetitionPattern"&gt;&lt;PROPERTY NAME="Duration" TYPE="string"&gt;&lt;/PROPERTY&gt;&lt;PROPERTY NAME="Interval" TYPE="string"&gt;&lt;/PROPERTY&gt;&lt;PROPERTY NAME="StopAtDurationEnd" TYPE="boolean"&gt;&lt;/PROPERTY&gt;&lt;/CLASS&gt;</S>
            </MS>
        </Obj>
        </LST>
    </Obj>
    </MS>
</Obj>
</Objs>
'@

        $repetitionObject = [System.Management.Automation.PSSerializer]::Deserialize($object)
        #endregion Repetitions (ugly)
        
        #region Setup Task Configuration
        if (-not $NoTask)
        {
            $action = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -Command Invoke-MDDaemon"
            $triggers = @()
            $triggers += New-ScheduledTaskTrigger -AtStartup -RandomDelay "00:15:00"
            $triggers += New-ScheduledTaskTrigger -At "00:00:00" -Daily
            
            if ($TaskUser) { $principal = New-ScheduledTaskPrincipal -UserId $TaskUser.UserName -LogonType Interactive }
            else { $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType Interactive }
            
            $taskItem = New-ScheduledTask -Action $action -Principal $principal -Trigger $triggers -Description "Mail Daemon task, checks for emails to send at a specified interval. Uses the internal MailDaemon module."
            $taskItem.Author = Get-PSFConfigValue -FullName 'MailDaemon.Task.Author' -Fallback "$($env:USERDOMAIN) IT Department"
            $taskItem.Triggers[1].Repetition = $repetitionObject
            
            $parametersRegister = @{
                TaskName    = 'MailDaemon'
                InputObject = $taskItem
            }
            if ($TaskUser)
            {
                $parametersRegister["User"] = $TaskUser.UserName
                $parametersRegister["Password"] = $TaskUser.GetNetworkCredential().Password
            }
        }
        #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]
        }
        
        $paramMainInstallCall = @{
            ArgumentList = $parameters
            Credential   = $Credential
        }
        #endregion Preparing Parameters
        
        #region The Main Setup Scriptblock
        $paramMainInstallCall["ScriptBlock"] = {
            param (
                $Parameters
            )
            
            Import-Module -Name PSFramework
            Import-Module -Name MailDaemon
            
            Set-MDDaemon @parameters
            
            #region Set file permissions
            if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath'))) { $null = New-Item (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') -Force -ItemType Directory }
            if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath'))) { $null = New-Item (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath') -Force -ItemType Directory }
            
            if ($Parameters.DaemonUser) { Update-MDFolderPermission -DaemonUser $Parameters.DaemonUser }
            if ($Parameters.WriteUser) { Update-MDFolderPermission -WriteUser $Parameters.WriteUser }
            #endregion Set file permissions
        }
        #endregion The Main Setup Scriptblock
    }
    
    process
    {
        #region Ensure Modules are installed
        $testResults = Test-Module -ComputerName $ComputerName -Credential $Credential -Module @{
            MailDaemon  = $script:ModuleVersion
            PSFramework = (Get-Module -Name PSFramework).Version
        }
        
        $failedTests = $testResults | Where-Object Success -EQ $false
        
        if ($failedTests)
        {
            $grouped = $failedTests | Group-Object Name
            foreach ($groupSet in $grouped)
            {
                Copy-Module -ModuleName (Get-Module $groupSet.Name).ModuleBase -ToComputer $groupSet.Group.ComputerName
            }
        }
        #endregion Ensure Modules are installed
        
        $paramMainInstallCall['ComputerName'] = $ComputerName
        
        Invoke-PSFCommand @paramMainInstallCall
        
        #region Securely store credentials
        if ($PSBoundParameters.ContainsKey('SenderCredential'))
        {
            $parametersSave = @{
                ComputerName = $ComputerName
                Credential   = $SenderCredential
                Path         = 'C:\ProgramData\PowerShell\MailDaemon\senderCredentials.clixml'
            }
            if ($TaskUser) { $parametersSave['AccessAccount'] = $TaskUser }
            Save-MDCredential @parametersSave
            
            $parametersInvoke = @{ $parametersInvoke['ComputerName'] = $ComputerName }
            Invoke-PSFCommand @parametersInvoke -ScriptBlock {
                Set-MDDaemon -SenderCredentialPath "C:\ProgramData\PowerShell\MailDaemon\senderCredentials.clixml"
            }
        }
        #endregion Securely store credentials
        
        #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
            }
        }
        #endregion Setup Task
    }
}

function Invoke-MDDaemon
{
    <#
        .SYNOPSIS
            Processes the email queue and sends emails
 
        .DESCRIPTION
            Processes the email queue and sends emails.
            Should be scheduled using a scheduled task.
            Recommended Setting:
            - Launch on boot with delay
            - Launch on Midnight
            - Repeat every 30 minutes for one day
 
        .EXAMPLE
            PS C:\> Invoke-MDDaemon
 
            Processes the email queue and sends emails
    #>

    [CmdletBinding()]
    Param (
    
    )
    
    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')))
        {
            $null = New-Item -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath') -ItemType Directory
        }
    }
    process
    {
        #region Send mails
        foreach ($item in (Get-ChildItem -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath')))
        {
            $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)
                ErrorAction = 'Stop'
            }
            if ($email.To) { $parameters["To"] = $email.To }
            else { $parameters["To"] = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.RecipientDefault' }
            if ($email.From) { $parameters["From"] = $email.From }
            else { $parameters["From"] = Get-PSFConfigValue -FullName 'MailDaemon.Daemon.SenderDefault' }
            if ($email.Cc) { $parameters["Cc"] = $email.Cc }
            if ($email.Subject) { $parameters["Subject"] = $email.Subject }
            else { $parameters["Subject"] = "<no subject>" }
            if ($email.Body) { $parameters["Body"] = $email.Body }
            if ($null -ne $email.BodyAsHtml) { $parameters["BodyAsHtml"] = $email.BodyAsHtml }
            if ($email.Attachments) { $parameters["Attachments"] = $email.Attachments }
            if ($script:_Config.SenderCredentialPath) { $parameters["Credential"] = Import-Clixml (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 }
            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-Item $attachment -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
            {
                Write-PSFMessage -Level Warning -String 'Invoke-MDDaemon.ManageSuccessJob.Failed' -StringValues $email.Taskname -Target $email.Taskname
            }
        }
        #endregion Send mails
    }
    end
    {
        #region Cleanup expired mails
        foreach ($item in (Get-ChildItem -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath')))
        {
            if ($item.LastWriteTime -lt (Get-Date).Add((-1 * (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentRetention'))))
            {
                Remove-Item $item.FullName
            }
        }
        #endregion Cleanup expired mails
    }
}


function Save-MDCredential
{
    <#
        .SYNOPSIS
            Stores credentials securely for use by the specified account.
 
        .DESCRIPTION
            This command encrypts credentials to a protected credentials file in the file system.
            This is designed to allow storing credential objects for use by scheduled task that run as SYSTEM or a service account.
 
        .PARAMETER TargetCredential
            The credentials to encrypt and write to file.
 
        .PARAMETER Path
            The path where to store the credential.
            Always considered as local path from the computer it is registered on.
 
        .PARAMETER AccessAccount
            The account that should have access to the credential.
            Defaults to the system account.
            Offer a full credentials object for a regular user account.
 
        .PARAMETER ComputerName
            The computer(s) to write the credential to.
     
        .PARAMETER Credential
            The credentials to use to authenticate to the target system.
            NOT the credentials stored for reuse.
 
        .EXAMPLE
            PS C:\> Save-MDCredential -ComputerName DC1,DC2,DC3 -TargetCredential $cred -Path "C:\ProgramData\PowerShell\Tasks\tesk1_credential.clixml"
 
            Saves the credentials stored in $cred on the computers DC1, DC2, DC3 for use by the SYSTEM account
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [PSCredential]
        $TargetCredential,

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [PSCredential]
        $AccessAccount,

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PSFComputer[]]
        $ComputerName = $env:COMPUTERNAME,
        
        [PSCredential]
        $Credential
    )

    process
    {
        $parameters = @{
            ArgumentList = $TargetCredential, $Path, $AccessAccount
            ComputerName = $ComputerName
            Credential = $Credential
        }
        
        Invoke-PSFCommand @parameters -ScriptBlock {
            Param (
                [PSCredential]
                $Credential,

                [string]
                $Path,

                [PSCredential]
                $AccessAccount
            )

            #region Folder Management
            if (Test-Path -Path $Path)
            {
                $item = Get-Item $Path
                if ($item.PSIsContainer)
                {
                    $folder = $item.FullName
                    $file = Join-Path $folder 'Credential.clixml'
                }
                else
                {
                    $folder = Split-Path $item.FullName
                    $file = $item.FullName
                }
            }
            else
            {
                if ([System.IO.Path]::GetExtension($Path))
                {
                    $folder = Split-Path $Path
                    $file = $Path
                }
                else
                {
                    $folder = $Path
                    $file = Join-Path $folder 'Credential.clixml'
                }
            }
            if (-not (Test-Path -Path $folder))
            {
                $null = New-Item -Path $folder -ItemType Directory -Force -ErrorAction Stop
            }
            #endregion Folder Management

            #region Access Privileges
            $accessUserName = $AccessAccount.UserName
            if (-not $accessUserName) { $accessUserName = "SYSTEM" }
            $acl = Get-Acl -Path $folder
            if (-not ($acl.Access | Where-Object IdentityReference -like $accessUserName | Where-Object {
                ($_.FileSystemRights -Band 278) -and ($_.FileSystemRights -Band 65536)
            }))
            {
                $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($accessUserName, 'Read, Write', 'Allow')
                $null = $acl.AddAccessRule($rule)
                $acl | Set-Acl -Path $folder
            }
            #endregion Access Privileges

            #region Create Task
            $folderCleaned = (Get-Item $folder).FullName
            $credFile = "{0}\{1}.txt" -f $folderCleaned, ([guid]::NewGuid())
            $task = {
                $password = [System.IO.File]::ReadAllText("<credfile>")
                Remove-Item -Path "<credfile>"
                $credential = New-Object PSCredential("<username>", ($password | ConvertTo-SecureString -AsPlainText -Force))
                $credential | Export-Clixml -Path "<exportPath>"
            }
            $commandString = $task.ToString().Replace("<credfile>", $credFile).Replace("<username>", $Credential.UserName).Replace("<exportPath>", $file)
            $encodedCommand = [convert]::ToBase64String(([System.Text.Encoding]::Unicode.GetBytes($commandString)))

            $action = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -EncodedCommand $encodedCommand"
            
            if ($accessUserName -ne "SYSTEM") { $principal = New-ScheduledTaskPrincipal -UserId $accessUserName -LogonType Interactive }
            else { $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType Interactive }
            $taskItem = New-ScheduledTask -Action $action -Principal $principal -Description "Temporary Task"
            $parametersRegister = @{
                TaskName = "TempTask_$([guid]::NewGuid())"
                InputObject = $taskItem
            }
            if ($accessUserName -ne "SYSTEM")
            {
                $parametersRegister["User"] = $AccessAccount.UserName
                $parametersRegister["Password"] = $AccessAccount.GetNetworkCredential().Password
            }
            $null = Register-ScheduledTask @parametersRegister
            #endregion Create Task

            #region Perform Encryption
            [System.IO.File]::WriteAllText($credFile, $Credential.GetNetworkCredential().Password)
            Start-ScheduledTask -TaskName $parametersRegister.TaskName
            Start-Sleep -Seconds 5
            #endregion Perform Encryption

            #region Cleanup
            Unregister-ScheduledTask -TaskName $parametersRegister.TaskName -Confirm:$false
            if (Test-Path -Path $credFile)
            {
                try { Remove-Item $credFile -Force -ErrorAction Stop }
                catch
                {
                    Write-Warning "[$env:COMPUTERNAME] Clear Text Credential File still exists!! $credFile | $_"
                }
            }
            if (-not (Test-Path -Path $file))
            {
                throw "[$env:COMPUTERNAME] Failed to create credential file! ($file)"
            }
            #endregion Cleanup
        }
    }
}


function Send-MDMail
{
    <#
        .SYNOPSIS
            Queues current email for delivery.
 
        .DESCRIPTION
            Uses the data prepared by Set-MDMail or Add-MDMailContent and queues the email for delivery.
 
        .PARAMETER TaskName
            Name of the task that is sending the email.
            Used in the name of the file used to queue messages in order to reduce likelyhood of accidental clash.
         
        .EXAMPLE
            PS C:\> Send-MDMail -TaskName "Logrotate"
 
            Queues the currently prepared email under the name "Logrotate"
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [string]
        $TaskName
    )
    
    begin
    {
        # Ensure the pickup patch exists
        if (-not (Test-Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath')))
        {
            try { $null = New-Item -Path (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') -ItemType Directory -Force -ErrorAction Stop }
            catch
            {
                Stop-PSFFunction -String 'Send-MDMail.Folder.CreationFailed' -StringValues (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath') -ErrorRecord $_ -Cmdlet $PSCmdlet -EnableException $true
            }
        }
    }
    process
    {
        # Don't send an email if nothing was set up
        if (-not $script:mail) { Stop-PSFFunction -String 'Send-MDMail.Email.NotRegisteredYet' -EnableException $true -Cmdlet $PSCmdlet }

        $script:mail['Taskname'] = $TaskName
        
        # Send the email
        Write-PSFMessage -String 'Send-MDMail.Email.Sending' -StringValues $TaskName -Target $TaskName
        try { [PSCustomObject]$script:mail | Export-Clixml -Path "$(Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath')\$($TaskName)-$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss').clixml" -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -String 'Send-MDMail.Email.SendingFailed' -StringValues $TaskName -ErrorRecord $_ -Cmdlet $PSCmdlet -EnableException $true -Target $TaskName
        }

        # Reset email, now that it is queued
        $script:mail = $null

        try { Start-ScheduledTask -TaskName MailDaemon -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -String 'Send-MDMail.Email.TriggerFailed' -StringValues $TaskName -ErrorRecord $_ -Cmdlet $PSCmdlet -EnableException $true -Target $TaskName
        }
    }
}


function Set-MDDaemon
{
<#
    .SYNOPSIS
        Configures the Daemon settings on the target computer(s)
     
    .DESCRIPTION
        Command that governs the Mail Daemon settings.
     
    .PARAMETER PickupPath
        The folder in which emails are queued for delivery.
     
    .PARAMETER SentPath
        The folder in which emails that were successfully sent are stored for a specified time before being deleted.
     
    .PARAMETER MailSentRetention
        The time to keep successfully sent emails around.
     
    .PARAMETER SmtpServer
        The mailserver to use for sending emails.
     
    .PARAMETER SenderDefault
        The default email address to use as sender.
        This is used for mails queued by a task that did not specify a sender.
     
    .PARAMETER RecipientDefault
        Default email address to send the email to, if the individual script queuing the email does not specify one.
     
    .PARAMETER SenderCredentialPath
        The path to where the credentials file can be found, that should be used by the daemon.
     
    .PARAMETER ComputerName
        The computer(s) to work against.
        Defaults to localhost, but can be used to update the module settings across a wide range of computers.
     
    .PARAMETER Credential
        The credentials to use when connecting to computers.
     
    .EXAMPLE
        PS C:\> Set-MDDaemon -PickupPath 'C:\MailDaemon\Pickup'
         
        Updates the configuration to now pickup incoming emails from 'C:\MailDaemon\Pickup'.
        Will not move pending email jobs.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    [CmdletBinding()]
    param (
        [string]
        $PickupPath,
        
        [string]
        $SentPath,
        
        [Timespan]
        $MailSentRetention,
        
        [string]
        $SmtpServer,
        
        [string]
        $SenderDefault,
        
        [string]
        $RecipientDefault,
        
        [string]
        $SenderCredentialPath,
        
        [Parameter(ValueFromPipeline = $true)]
        [PSFComputer[]]
        $ComputerName = $env:COMPUTERNAME,
        
        [PSCredential]
        $Credential
    )
    
    begin
    {
        #region Configuration Script
        $configurationScript = {
            param (
                $Parameters
            )
            
            # Import module so settings are initialized
            if (-not (Get-Module MailDaemon)) { Import-Module MailDaemon }
            
            foreach ($key in $Parameters.Keys)
            {
                Write-PSFMessage -String 'Set-MDDaemon.UpdateSetting' -StringValues $key, $Parameters[$key]
                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'
                    {
                        Set-PSFConfig -Module MailDaemon -Name 'Daemon.MailSentPath' -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] }
                    '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] }
                    'RecipientDefault' { Set-PSFConfig -Module MailDaemon -Name 'Daemon.RecipientDefault' -Value $Parameters[$key] }
                }
            }
            
            Get-PSFConfig -Module MailDaemon -Name Daemon.* | Where-Object Unchanged -EQ $false | Register-PSFConfig -Scope FileSystem
        }
        #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
    }
    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)
        {
            Stop-PSFFunction -String 'General.ModuleMissing' -StringValues ($moduleResult.ComputerName -join ", ") -EnableException $true -Cmdlet $PSCmdlet
        }
        #endregion Modules must be installed and current
        
        Write-PSFMessage -String 'Set-MDDaemon.UpdatingSettings' -StringValues ($ComputerName -join ", ")
        Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $configurationScript -ArgumentList $parameters
    }
}

function Set-MDMail
{
    <#
        .SYNOPSIS
            Changes properties for the upcoming mail to queue.
 
        .DESCRIPTION
            This command sets up the email to send, configuring properties such as the sender, recipient or content.
 
        .PARAMETER From
            The email address of the sender.
 
        .PARAMETER To
            The email address to send to.
 
        .PARAMETER Cc
            Additional addresses to keep in the information flow.
 
        .PARAMETER Subject
            The subject to send the email under.
 
        .PARAMETER Body
            The body of the email to send.
            You can individually add content to the body using Add-MDMailContent.
         
        .PARAMETER BodyAsHtml
            Whether the body is to be understood as html text.
 
        .PARAMETER Attachments
            Any attachments to send.
            Avoid sending large attachments with emails.
            You can individually add attachments to the email using Add-MDMailContent (using this parameter will replace attachments sent).
         
        .PARAMETER RemoveAttachments
            After sending the email, remove the attachments sent.
            Use this to have the system clean up temporary files you wrote before sending this report.
 
        .PARAMETER NotBefore
            Do not send this email before this timestamp has come to pass.
 
        .EXAMPLE
            PS C:\> Set-MDMail -From 'script@contoso.com' -To 'support@contoso.com' -Subject 'Daily Update Report' -Body $body
 
            Sends an email as script@contoso.com to support@contoso.com, reporting on the daily update status.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    Param (
        [string]
        $From,

        [string]
        $To,

        [string[]]
        $Cc,

        [string]
        $Subject,

        [string]
        $Body,

        [switch]
        $BodyAsHtml,

        [string]
        $Attachments,

        [switch]
        $RemoveAttachments,

        [datetime]
        $NotBefore
    )
    
    begin
    {
        if (-not $script:mail)
        {
            $script:mail = @{ }
        }
    }
    process
    {
        if ($From) { $script:mail["From"] = $From }
        if ($To) { $script:mail["To"] = $To }
        if ($Cc) { $script:mail["Cc"] = $Cc }
        if ($Subject) { $script:mail["Subject"] = $Subject }
        if ($Body) { $script:mail["Body"] = $Body }
        if ($BodyAsHtml.IsPresent) { $script:mail["BodyAsHtml"] = ([bool]$BodyAsHtml) }
        if ($Attachments) { $script:mail["Attachments"] = $Attachments }
        if ($RemoveAttachments.IsPresent) { $script:mail["RemoveAttachments"] = ([bool]$RemoveAttachments) }
        if ($NotBefore) { $script:mail["NotBefore"] = $NotBefore }
    }
}


function Update-MDFolderPermission
{
<#
    .SYNOPSIS
        Assigns permissions for the mail daemon working folders.
     
    .DESCRIPTION
        Assigns permissions for the mail daemon working folders.
        Enables simple assignment of privileges in case regular accounts need to write to protected pickup paths and helps implementing least privilege.
     
    .PARAMETER ComputerName
        The computer(s) to work against.
        Defaults to localhost.
     
    .PARAMETER Credential
        The credentials to use when connecting to computers.
     
    .PARAMETER DaemonUser
        The user to grant the necessary access to manage submitted mail items.
     
    .PARAMETER WriteUser
        Users that should be able to submit mails.
     
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
     
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
     
    .EXAMPLE
        PS C:\> Update-MDFolderPermission -DaemonUser 'domain\srv_server1mail$'
     
        Grants Daemon User privileges on the local computer to the service account 'domain\srv_server1mail$'
#>

    [CmdletBinding(SupportsShouldProcess = $true)]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [PSFComputer[]]
        $ComputerName = $env:COMPUTERNAME,
        
        [PSCredential]
        $Credential,
        
        [string]
        $DaemonUser = " ",
        
        [string[]]
        $WriteUser = " "
    )
    
    begin
    {
        #region Permission Assigning Scriptblock
        $permissionScript = {
            param (
                [string]
                $DaemonUser,
                
                [string[]]
                $WriteUser
            )
            
            Import-Module MailDaemon
            
            $pickupPath = (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailPickupPath')
            $sentPath = (Get-PSFConfigValue -FullName 'MailDaemon.Daemon.MailSentPath')
            
            if ($DaemonUser.Trim())
            {
                Write-PSFMessage -String 'Update-MDFolderPermission.Granting.DaemonUser' -StringValues $DaemonUser, $pickupPath, $sentPath
                $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($DaemonUser, 'Read, Write', 'Allow')
                
                $acl = Get-Acl -Path $pickupPath
                $acl.AddAccessRule($rule)
                $acl | Set-Acl -Path $pickupPath
                $acl = Get-Acl -Path $sentPath
                $acl.AddAccessRule($rule)
                $acl | Set-Acl -Path $sentPath
            }
            foreach ($user in $WriteUser)
            {
                if ($user.Trim()) { continue }
                Write-PSFMessage -String 'Update-MDFolderPermission.Granting.WriteUser' -StringValues $user, $pickupPath
                $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($user, 'Write', 'Allow')
                
                $acl = Get-Acl -Path $pickupPath
                $acl.AddAccessRule($rule)
                $acl | Set-Acl -Path $pickupPath
            }
        }
        #endregion Permission Assigning Scriptblock
    }
    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)
        {
            Stop-PSFFunction -String 'General.ModuleMissing' -StringValues ($moduleResult.ComputerName -join ", ") -EnableException $true -Cmdlet $PSCmdlet
        }
        #endregion Modules must be installed and current
        
        if (Test-PSFShouldProcess -PSCmdlet $PSCmdlet -Target ($ComputerName -join ", ") -Action "Granting the write permissions needed by the Daemon User ($($DaemonUser)) and Write User ($($WriteUser -join ', '))")
        {
            Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $permissionScript -ArgumentList $DaemonUser, $WriteUser
        }
    }
}

<#
This is an example configuration file
 
By default, it is enough to have a single one of them,
however if you have enough configuration settings to justify having multiple copies of it,
feel totally free to split them into multiple files.
#>


<#
# Example Configuration
Set-PSFConfig -Module 'MailDaemon' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'"
#>


Set-PSFConfig -Module 'MailDaemon' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'MailDaemon' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."

Set-PSFConfig -Module 'MailDaemon' -Name 'Task.Author' -Value "$($env:USERDOMAIN) IT Department" -Initialize -Validation 'string' -SimpleExport -Description 'When setting up the scheduled task using Install-MDDaemon, this is the name used as the author of the scheduled task'

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

<#
Stored scriptblocks are available in [PsfValidateScript()] attributes.
This makes it easier to centrally provide the same scriptblock multiple times,
without having to maintain it in separate locations.
 
It also prevents lengthy validation scriptblocks from making your parameter block
hard to read.
 
Set-PSFScriptblock -Name 'MailDaemon.ScriptBlockName' -Scriptblock {
     
}
#>


<#
# Example:
Register-PSFTeppScriptblock -Name "MailDaemon.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
#>


<#
# Example:
Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name MailDaemon.alcohol
#>


New-PSFLicense -Product 'MailDaemon' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2019-02-08") -Text @"
Copyright (c) 2019 Friedrich Weinmann
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@

#endregion Load compiled code