The key update in MDaemon is not going through

Please fill out the fields below so we can help you better. Note: you must provide your domain name to get help. Domain names for issued certificates are all made public in Certificate Transparency logs (e.g. crt.sh | example.com), so withholding your domain name here does not increase secrecy, but only makes it harder for us to provide help.

My domain is: mail.primeshipping.ru

I ran this command:

'''
################################################################################################################################
################################################################################################################################
## Retrieve Certificate from Lets Encrypt  V2.0.12                                                                            ##
## August 14, 2023  																	                                          ##
## Copyright MDaemon Technologies	2023												                                      ##
##                                                                                                                            ##
## This script is designed to retrieve a certificate from LetsEncrypt and then configure Alt-N's products to use it.  This    ##
## script is only to be used with Alt-N products. The use of this script for any other purpose or with any other products     ##
## is not allowed.                                                                                                            ##
##                                                                                                                            ##
## MDaemon Technologies offers no warranty, provides no support, and is not responsible for any damages that may be arise     ##
## from the use of this script. Use at your own risk.                                                                         ##
##                                                                                                                            ##
################################################################################################################################
################################################################################################################################
#Example usage: .\LetsEncrypt.ps1 -AlternateHostNames mail.domain.com,imap.domain.com,wc.domain.com -IISSiteName MySite -To "admin@yourdomain.com" -RemoveOldCertificates

param(
    [Parameter(Position = 0)]
    [string[]]$AlternateHostNames,
    [string]$IISSiteName = "WorldClient",
    [string]$To = "",
	[switch]$RemoveOldCertificates,
	[switch]$ECDSA,
	[switch]$Staging,
	[switch]$SkipPortCheck,
    [string]$ExternalchallengeFilePath = ""
    )
# This command enables TLS 1.1, 1.2, and 1.3 (if supported).  This only changes the value for this session.
#PowerShell also honors the operating system settings for client SSL/TLS protocol support, so if you disable support for TLS 1.1 as a client 
#protocol in the operating sytem, PowerShell will not attempt to use it.
if(([Net.SecurityProtocolType].GetEnumNames()) -contains "Tls13"){
    [Net.ServicePointManager]::SecurityProtocol = `
    [Net.SecurityProtocolType]::Tls13,
    [Net.SecurityProtocolType]::Tls12,
    [Net.SecurityProtocolType]::Tls11;
} else {
    [Net.ServicePointManager]::SecurityProtocol = `
    [Net.SecurityProtocolType]::Tls12,
    [Net.SecurityProtocolType]::Tls11;
}
clear
$error.clear()

$ErrorActionPreference = "SilentlyContinue"

# if these variables are not in the global namespace, I saw concurrency issues
$global:ErrorSent = ""
$global:LoggedScriptStarting = ""

#This will be used to determine if a restart of MDaemon and all other components is required.
$Restart = $false

#Setting up a global array to store the aliases for Alternate domain names
$global:AlternateHostNameAliases = @()
$global:Identifiers = @()
$global:CertKey = ""
$HostNames = @()

function Get-PrivateProfileString ($Path, $Category, $Key) {

	#############################################################################
	##
	## Get-PrivateProfileString
	##
	## From Windows PowerShell Cookbook (O'Reilly)
	## by Lee Holmes (http://www.leeholmes.com/guide)
	##
	##############################################################################
 
	<#
 
	.SYNOPSIS
 
	Retrieves an element from a standard .INI file
 
	.EXAMPLE
 
	Get-PrivateProfileString c:\windows\system32\tcpmon.ini `
		"<Generic Network Card>" Name
	Generic Network Card
 
	#>
 
	Set-StrictMode -Version Latest
 
	## The signature of the Windows API that retrieves INI
	## settings
	$signature = @'
[DllImport("kernel32.dll")]
public static extern uint GetPrivateProfileString(
   string lpAppName,
   string lpKeyName,
   string lpDefault,
   StringBuilder lpReturnedString,
   uint nSize,
   string lpFileName);
'@
 
	## Create a new type that lets us access the Windows API function
	$type = Add-Type -MemberDefinition $signature `
		-Name Win32Utils -Namespace GetPrivateProfileString `
		-Using System.Text -PassThru
 
	## The GetPrivateProfileString function needs a StringBuilder to hold
	## its output. Create one, and then invoke the method
	$builder = New-Object System.Text.StringBuilder 1024
	$null = $type::GetPrivateProfileString($category,
		$key, "", $builder, $builder.Capacity, $path)
 
	## Return the output
	$builder.ToString()

}

function Write-PrivateProfileString ($Path, $Category, $Key, $Value) {

    #Log "The path is: $Path"
    #Log "The category is: $Category"
    #Log "The key is: $Key"
    #Log "The vlaue is: $Value"

	Set-StrictMode -Version Latest
 
	## The signature of the Windows API that retrieves INI
	## settings
	$signature = @'
[DllImport("kernel32.dll")]
public static extern uint WritePrivateProfileString(
   string lpAppName,
   string lpKeyName,
   string lpDefault,
   string lpFileName);
'@
 
	## Create a new type that lets us access the Windows API function
	$type = Add-Type -MemberDefinition $signature `
		-Name Win32Utils -Namespace WritePrivateProfileString `
		-Using System.Text -PassThru
 
	## The GetPrivateProfileString function needs a StringBuilder to hold
	## its output. Create one, and then invoke the method
	$builder = New-Object System.Text.StringBuilder 1024
	$null = $type::WritePrivateProfileString($category,
		$key, $value, $path)
 
	## Return the output
	#$builder.ToString()

}

# Log to console and log file and handle error
function Log($string, $color){    
    LogNoError $string $color
    
    if($string -ne $null)
    {
        if($error -and ($global:ErrorSent -ne "Yes"))
        {
            $error
            $error.clear()
            
            $global:ErrorSent = "Yes"

            if ($To)
            {
                $EmailBody = $EmailBody + "`r`n`r`n" + $string
                SendEmail $email $To $EmailSubject $EmailBody 
            }
            
            LogNoError "The script is stopping because an error occurred." "Red" 
            
            exit 1
        }
    }
}

# Only log to console and log file
# Use to allow the script to continue even if an error has occured
function LogNoError($string, $color){
    if($string -ne $null)
    {
        if($global:LoggedScriptStarting -ne "Yes")
        {
            $global:LoggedScriptStarting = "Yes"
            LogNoError "Starting Script run at $MyDate.`r`n"
        }

        if ($color -eq $null) 
        {
            $color = "White"
        }
        
        write-host $string -ForegroundColor $color 
        
        if ($LetsEncryptLog -ne $null)
        {
            $string | Add-Content -Path $LetsEncryptLog
        }
    }
}

function CheckWebScriptingInstalled {

    $WebScriptingInstalled = get-windowsfeature|where{$_.name -eq "Web-Scripting-Tools"}

    Write-Host $WebScriptingInstalled
    if($WebScriptingInstalled.Installed)
    {
        Log "Web Scripting tools are installed."
        Return $true
    }
    elseif(!($WebScriptingInstalled.Installed))
    {
        Log "Error: Web Scripting tools are not installed."
        Return $false
    }
    else
    {
        Log "Error: Unable to determine if the Web Scripting Tools are installed."
        Return $false
    }
}

function SendEmail ($From, $To, $Subject, $Body){
    if($To -eq "")
    {
        Log "Unable to send an error email. No To address was specified."
    }
    else
    {
        $FileNumber = 1
        $RawFile = Join-Path $RAWDir "md50$FileNumber.raw"

        while(Test-Path $RawFile)
        {
            $FileNumber = $FileNumber + 1
            $RawFile = Join-Path $RAWDir "md50$FileNumber.raw"
        }

        $FileContent = "from <$From>`r`nto <$To>`r`nsubject <$Subject>`r`nHeader <Content-Type: text/html>`r`n`r`n$Body"
        New-Item -Path $RawFile -type file -value $FileContent -Force
    }
 
}

function CreateOrder($StatePath, $FQDN){
    Log "Creating new certificate."

    Write-Host "Getting another updated state, just in case it has changed."
	$State = Get-AcmeState -Path $StatePath

	Log "Creating a new order for $FQDN using $global:Identifiers"
	#Create the order object at the ACME service.
	$Order = New-ACMEOrder -State $State -Identifiers $global:Identifiers -ErrorVariable LogText
    Log $LogText


	return $Order
}

function GetRegistryValue($Key, $Name){

	Log "Checking $Key"
    if(Test-Path $Key)
    {
        $Value = (Get-ItemProperty $Key -Name $Name).$Name

		if($Value -eq $null)
        {
            #This is included to clear the error that is set when the $Key exists but the $Name does not.
            $Error.Clear()
        }
	}
	
	if(!(Test-Path $Key) -or ($Value -eq $null) -or ($Value.Length -eq 0))
    {
                
		$Base = Split-Path (Split-Path $Key)
        $Leaf2 = Split-Path $key -Leaf
        $Leaf1 = Split-Path (Split-Path $key) -Leaf
                
        $SysWownode = Join-Path (Join-Path (Join-Path $Base "Wow6432Node") $Leaf1) $Leaf2

        if(Test-Path $SysWownode)
        {
			Log "The registry key value, $Key\$Name, is empty or does not exist, checking $SysWownode."
            $Value = (Get-ItemProperty $SysWowNode -Name $Name).$Name

            if(($Value -eq $null) -or ($Value.Length -eq 0))
            {
				Log "We can't find the registry key values, the script will now stop."
				exit 1
            }
            else
            {
				$Error.Clear()
                return $Value
            }
        }
        else
        {
			Log "We can't find the registry key values, the script will now stop."
			exit 1
        }
    }
    else
    {
		return $Value
    } 
}

function Stop-ServiceWithTimeout($Name, $TimeOut){
	$TimeSpan = New-Object -TypeName System.Timespan -ArgumentList 0,0,$TimeOut
	$Service = Get-Service -Name $Name
	if($Service -eq $null)
	{
		return $false
	}

	if($Service.Status -eq [ServiceProcess.ServiceControllerStatus]::Stopped)
	{ 
		return $true
	}

	$Service.Stop()

	try
	{
		$Service.WaitForStatus([ServiceProcess.ServiceControllerStatus]::Stopped, $TimeSpan)
	}
	catch [ServiceProcess.TimeoutException] 
	{
		Log "The $($Service.Name) service did not stop in a timely manner."
		return $false
	}
	return $true
}

function GetAuthorizations ($State, $Order) {
	Log "Getting an authorization for the $($Order.Identifiers)."
	$AuthZ = Get-ACMEAuthorization $State -Order $Order 
        
    
	return $AuthZ
}

function CompleteChallenges ($State, $Authorizations, $Order) {
	
	Log "The .well-known path for is $WellKnownPath"
    Log "The Acme Challenge path for $AcmeChallengePath"

    if(!(Test-Path $wellKnownPath))
    {
        Log "The path $wellKnownPath does not exist, it will be created."
        New-Item $wellKnownPath -type directory
    }
    if(!(Test-Path $AcmeChallengePath))
    {
        Log "The path $AcmeChallengePath does not exist, it will be created."
        New-Item $AcmeChallengePath -type directory
    }

	foreach($Authorization in $Authorizations){
		Log "Selecting the http-01 challenge and getting challenge data for $($Authorization.Identifier)."
		$challenge = Get-ACMEChallenge $State $Authorization "http-01"

        Write-Host "Creating challenge file."

		$ChallengeFileName = $Challenge.Data.FileName
        $ChallengeContent = $Challenge.Data.Content

        if($ChallengeFileName -eq $null)
        {
			$Message = "The challenge file name is empty for $($Challenge.Identifier)."
			$MessageText = $EmailBody + "`r`n`r`n" + $Message
            Log  $Message "Red"
            Log "This is a critical error, the script will now stop." "Red"

			SendEmail $email $To $EmailSubject $MessageText 
			exit 1
        } else {
            $ChallengeFilePath = Join-Path $AcmeChallengePath $ChallengeFileName
        }

		Log "The challenge status URL is $($Challenge.URL)."
		Log "The challenge identifier is $($Challenge.Identifier)."
		Log "The URL to verify the challenge is $($challenge.Data.AbsoluteURL)."
        Log "The Challenge file name for $($Challenge.Identifier) is $ChallengeFileName"
        Log "The Challenge Content for $($Challenge.Identifier) is $ChallengeContent"

        if(Test-Path $ChallengeFilePath)
        {
            Log "The Challenge file name for $($Challenge.Identifier) already exists, removing file."
            Remove-Item $ChallengeFilePath
        }

        Log "Creating $ChallengeFilePath for $($Challenge.Identifier)."
        New-Item -Path $ChallengeFilePath -type file -value $ChallengeContent -ErrorVariable LogText
        Log $LogText

        if(Test-Path $ChallengeFilePath){
            if($ExternalchallengeFilePath){
                if(Test-Path $ExternalchallengeFilePath){

                    Log "Copying $ChallengeFilePath to $ExternalchallengeFilePath"
                    Copy-Item $ChallengefilePath -Destination $ExternalchallengeFilePath
                } else {
                    Log "External Challenge File Path, $ExternalChallengeFilePath, is not valid."
                }
            } 
        }

		Log "Submitting the ACME challenge for $($Challenge.Identifier) for verification."
	    # Signal the ACME server that the challenge is ready
        $Challenge = Complete-ACMEChallenge $State -Challenge $Challenge -ErrorVariable LogText
	    Log $LogText
	}
}

function GetNewNonce ($State) {
	Log "Getting a new Nonce"
	## It might be neccessary to acquire a new nonce, so we'll do it just in case.
	New-ACMENonce $State -PassThru -ErrorVariable LogText
	Log $LogText
}

function UpdateState ($StatePath) {
	Log "Getting an updated state."
    $State = Get-AcmeState -Path $StatePath -ErrorVariable LogText
    Log $LogText

	return $State
}

function GetServiceDirectory ($State, $ServiceName) {

	Log "Getting service directory."
	Get-ACMEServiceDirectory $State -ServiceName $ServiceName -PassThru -ErrorVariable LogText
	Log $LogText
}

function GetCertKey ($Order, $CertKeyPath, $State) {
	$Count = 0
	while($Order.Status -notin ("ready","invalid", "valid") -and $Count -lt 12) {
        Start-Sleep -Seconds 10;
        $Order | Update-ACMEOrder $State -PassThru
		Log "Waiting for the order status to update... $Count"
        $Count++
	}

	if($Count -ge 12) {
		$Message = "Error: The order status is not updating. Please try again."
		$MessageText = $EmailBody + "`r`n`r`n" + $Message
		Log  $Message "Red"
		Log "This is a critical error, the script will now stop." "Red"

		SendEmail $email $To $EmailSubject $MessageText 
		exit 1
	}

	if($Order.Status -eq "invalid") {
		
		if($WCRunUnderIIS -eq "Yes"){
			$message = "Error: The challenge did not complete. 
			
			Most likely because IIS is not configured to handle extensionless static files. 
			Here is one way to fix that:
			1. Goto Site/Server->Mime Types
			2. Add a mime type of .* (text/plain)
			3. Goto Site/Server->Handler Mappings->View Ordered List
			4. Move the StaticFile mapping above the ExtensionlessUrlHandler mappings.
			Before making any changes, you should understand the security implications of doing so."

		} else {
			$Message = "Error: The challenge did not complete."
            
        }

        ForEach($URL in $Order.AuthorizationUrls) {

            $Response = Invoke-WebRequest -Uri $URL
            $Content = ConvertFrom-Json $Response.Content

            Foreach($Challenge in $Content.challenges | Where {$_.type -eq "http-01" -and $_.Status -eq "Invalid"}){
                
				$HostName = $challenge.validationRecord.hostname

                if($HostName -eq $null) {
                    Log "Validation record does not contain a host name. Getting a host name from the Indentifier."
                    $Hostname = $Content.identifier.value
                }

                $Message = $Message + "

                Host Name: $HostName
                Error Code: $($Challenge.error.status)
                Error Type: $($Challenge.error.type)
                Error Detail: $($Challenge.error.detail)"

            }

        }
		
		$MessageText = $EmailBody + "`r`n`r`n" + $Message
		Log  $Message "Red"
		Log "This is a critical error, the script will now stop." "Red"
		Log "Information obtained from the following URLs: $($Order.AuthorizationUrls)" "Red"

		SendEmail $email $To $EmailSubject $MessageText 

		exit 1
	} elseif ($Order.Status -eq "ready" -or $Order.Status -eq "valid") {

		# We should have a valid order now and should be able to complete it
		# Therefore we need a certificate key
        Log "Order is ready, getting the certificate key."
		if(Test-Path $CertKeyPath) {
            Log "Removing the existing $CertKeyPath file from disk."
			Remove-Item -Path $CertKeyPath -Force
		}
    
		if($ECDSA){
            $global:CertKey = New-ACMECertificateKey -Path $CertKeyPath -ECDsa
        } else {
    
		    $global:CertKey = New-ACMECertificateKey -Path $CertKeyPath -RSA
        }

    } else {
		
		$Message = "Error: The order status in an unexpected state.  Please try again.
			The order status is: $($Order.Status)"
		$MessageText = $EmailBody + "`r`n`r`n" + $Message
		Log  $Message "Red"
		Log "This is a critical error, the script will now stop." "Red"

		SendEmail $email $To $EmailSubject $MessageText 
		exit 1
    }
}

function CompleteOrder ($State, $Order) {
		
		Log "Completing order for $($Order.Identifiers)"
		# Complete the order - this will issue a certificate singing request
		Complete-ACMEOrder $State -Order $Order -CertificateKey $global:CertKey -ErrorVariable LogText
        Log $LogText

        $Count = 0
		# Now we wait until the ACME service provides the certificate url
		while(-not $Order.CertificateUrl -and $Count -lt 12) {
			Start-Sleep -Seconds 10
            Log "Waiting for the CertificateURL to be populated... $Count"
			$Order | Update-ACMEOrder $State -PassThru
            $count ++
		}

		if($Count -ge 12) {
			$Message = "Error: The certificateURL has not been populated yet. Please try again."
			$MessageText = $EmailBody + "`r`n`r`n" + $Message
			Log  $Message "Red"
			Log "This is a critical error, the script will now stop." "Red"

			SendEmail $email $To $EmailSubject $MessageText 
			exit 1
		}
        
		Log "The Certificate URL is $($Order.CertificateURL)"
}

function DownloadCertificate ($State, $Order, $CertOutput){
	
	Log "Exporting the certificate."
	# As soon as the url shows up we can create the PFX

	Export-ACMECertificate $State -Order $order -CertificateKey $global:CertKey -Path $CertOutput -Password $global:SecurePass

}

function GetIdentifier ($domain) {

    Log "Getting identifier for $domain."
    $Identifier = New-ACMEIdentifier $domain -ErrorVariable LogText
	Log $LogText

	$global:Identifiers += $Identifier
}

function CompareHostNames ($HostNames, $CertHostNames) {

	Log "Host names: $HostNames"
	Log "Certificate host names: $CertHostNames"
    if($CertHostNames -eq $null) {
        return $false
    } else {

        if(Compare-Object $HostNames $CertHostNames){
            Log "The list of alternate host names has changed. The alias for the certificate needs to be changed."
            return $false
        }
        else
        {
            Log "The list of alternate host names has not changed."
            return $true
        }
    }

}

function Get-CertificateThumbprint {
    # 
    # This will return a certificate thumbprint, null if the file isn't found or throw an exception.
    #

    param (
        [parameter(Mandatory = $true)][string] $CertificatePath,
        [parameter(Mandatory = $false)][string] $CertificatePassword
    )

    try {
        if (!(Test-Path $CertificatePath)) {
            return $null;
        }

        if ($CertificatePassword) {
            $sSecStrPassword = ConvertTo-SecureString -String $CertificatePassword -Force –AsPlainText
        }

		$certificateObject = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $sSecStrPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)

        return $certificateObject.Thumbprint
    } catch [Exception] {
        # 
        # Catch accounts already added.
        throw $_;
    }
}

function CheckPSVersion () {

	$psVersion = $PSVersionTable.PSVersion
	If ($psVersion)
	{
		if($psVersion.Major -ge 5) {
			if($psVersion.Major -eq 5) {
				if($psVersion.Minor -lt 1){
					Log "This script needs at least PowerShell 5.1 to work correctly! The script will now exit."
					exit 1
				}
			}
		} else {
			Log "This script needs at least PowerShell 5.1 to work correctly! The script will now exit."
			exit 1
		}    
	}
	Else{
		Log "This script needs at least PowerShell 5.1 to work correctly! The script will now exit."
		exit 1
	}

}

function CreateNewAccount ($StatePath, $ServiceName, $Email) {

    Log "Creating a new AcmeState."
    $State = New-AcmeState -Path $StatePath -ErrorVariable LogText
    Log $LogText

    GetServiceDirectory $State $ServiceName 

    GetNewNonce $State 

    Log "Creating a new Account Key."
    New-ACMEAccountKey $State -PassThru
        
    Log "Creating a new Account."
    New-ACMEAccount $State -EmailAddresses $Email -AcceptTOS -ExistingAccountIsError -ErrorVariable LogText
    Log $LogText

}

$MyDate = Get-Date
$MyYear = $MyDate.Year
$MyMonth = Get-Date -format MM
$MyTicks = $MyDate.Ticks
$ErrorSent = ""
$EmailBody = "An error occurred during the LetsEncrypt process.  The error message is:"
$EmailSubject = "Error Retrieving Certificate"
$global:SecurePass = ConvertTo-SecureString -String $MyTicks -AsPlainText -Force
$CertPassword = $MyTicks


$NewCert = $false

$MDINIPath = GetRegistryValue "HKLM:\SOFTWARE\Alt-N Technologies\MDaemon" "IniPath"
$APPPath = GetRegistryValue "HKLM:\SOFTWARE\Alt-N Technologies\MDaemon" "AppPath"
$WAINIPath = GetRegistryValue "HKLM:\SOFTWARE\Alt-N Technologies\WebAdmin" "SetupFile"
$WAPath = GetRegistryValue "HKLM:\SOFTWARE\Alt-N Technologies\WebAdmin" "Directory"

$MDPath = Split-Path -Path $APPPath -Parent
$PEMPath = Get-PrivateProfileString $MDINIPath "Directories" "PEM"
$StatePath = Join-Path $PEMPath "_LEState"
$LEPath = Join-Path $MDPath "LetsEncrypt"
$LEModulePath = Join-Path $LEPath "Modules"

$AirSyncINIPath = Join-Path $MDPath "Data\AirSync.ini"
$WCPath = Join-Path $MDPath "WorldClient"
$WCHTMLPath = Join-Path $WCPath "HTML"
$WCINIPath = Join-Path $WCPath "WorldClient.ini"
$wellKnownPath = Join-Path $WCHTMLPath ".well-known"
$AcmeChallengePath = Join-Path $wellKnownPath "Acme-challenge"


$WCRunUnderIIS = Get-PrivateProfileString $WCINIPath "WebServer" "RunUnderIIS"
$WCPort = Get-PrivateProfileString $WCINIPath "WebServer" "Port"
$WCEnabled = Get-PrivateProfileString $MDINIPath "WCServer" "EnableWCServer"
$ASEnabled = Get-PrivateProfileString $AirSyncINIPath "System" "Enable"
$FQDN = Get-PrivateProfileString $MDINIPath "Domain" "FQDN"
$RAWDir = Get-PrivateProfileString $MDINIPath "Directories" "RAW"
$MDLogPath = Get-PrivateProfileString $MDINIPath "Directories" "LogFiles"
$LetsEncryptLog = Join-Path $MDLogPath "LetsEncrypt.log"
$OrderPath = join-path $StatePath "Orders"
$OrderListPath = join-path $OrderPath "OrderList.txt"
$HostNames += $FQDN
if($AlternateHostNames.Length -gt 0){
    $HostNames += $AlternateHostNames
}
$CertKeyFileName = $FQDN + ".key.xml"
$CertKeyPath = Join-Path $StatePath $CertKeyFileName

$CertFileName = $FQDN + "_Cert" + "_" + $MyYear + "_" + $MyMonth + "_" + $MyTicks + ".pfx"
$CertOutput = Join-Path $PEMPath $CertFileName

$email = "postmaster@"+$FQDN

#if(Test-Path $LetsEncryptLog)
#{
#    Log "Removing the log from $LetsEncryptLog"
#    Remove-Item $LetsEncryptLog
#}

if(!(Test-Path $OrderPath)) {
    New-Item $OrderPath -type directory
}

if(!(Test-Path $OrderListPath)) {
    New-Item $OrderListPath -type file
}

Log "Starting Script run at $MyDate."
Log "Get the MDaemon paths."
Log "The MDaemon.ini Path is $MDINIPath."
Log "The MDaemon APP Path is $APPPath."
Log "The MDaemon Pem path is $PEMPath."
Log "The MDaemon Log path is $MDLogPath."
Log "The MDaemon RAW path is $RAWDir."
Log "The WorldClient Path is $WCPath."
Log "The WorldClient HTML Path is $WCHTMLPath."
Log "The well-known path is $wellKnownPath."
Log "The Acme-Challenge path is $AcmeChallengePath."
Log "The State Path is $StatePath."
Log "The FQDN is set to $FQDN."
Log "The email address is set to $email."

if($ECDSA) {
	Log "A Certificate was requested that uses ECDSA."
	if(!($Staging)){
		Log "Let's Encrypt is currently only supporting ECDSA certificates via their staging system and via an allowed accounts list in production."
		Log "If you'd like to request an ECDSA certificate from their production system, comment out lines 747 - 753."
		Log "For more information please see https://community.letsencrypt.org/t/ecdsa-availability-in-production-environment/150679"
		Log "The script will now exit." 
		exit 1
    }
}

#Set the service Name to LetsEncrypt-Staging for testing or LetsEncrypt for the live servers."
#If you set the Service Name to LetsEncrypt-Staging you also need to set LEName to Fake LE."
if($Staging) {
	Log "Setting the system to use the LetsEncrypt Staging Service."
	$ServiceName = "LetsEncrypt-Staging"
	#$LEName = "Fake LE"
	$LEName = "O=(STAGING) Let's Encrypt"
} else {
	Log "Setting the system to use the LetsEncrypt Live Service."
	$ServiceName = "LetsEncrypt"
	$LEName = "O=Let's Encrypt"
} 

CheckPSVersion

if($WCRunUnderIIS -ne "Yes" -and $WCEnabled -eq "No")
{
    $Message = "Error: WorldClient must be enabled. This script will stop now."
	$MessageText = $EmailBody + "`r`n`r`n" + $Message
    Log  $Message "Red"
    Log "This is a critical error, the script will now stop." "Red"

	SendEmail $email $To $EmailSubject $MessageText 

    Exit 1
}

if($SkipPortCheck){
    
    Log "Skipping the Port Check."

} else {
    if($WCRunUnderIIS -ne "Yes" -and $WCPort -ne "80" -and $ASEnabled -ne "Yes")
    {
	    $Message = "Error: WorldClient must be listening on port 80. This script will stop now."
	    $MessageText = $EmailBody + "`r`n`r`n" + $Message
        Log  $Message "Red"
        Log "This is a critical error, the script will now stop." "Red"

	    SendEmail $email $To $EmailSubject $MessageText 

        Exit 1
    }
}

#Get the current certificate hash from the MDaemon.ini file so we can compare them.
$MDCertificateHash = Get-PrivateProfileString $MDINIPath "SSL" "CertificateHash"

Log "The certificate thumbrpint in the MDaemon.ini file is $MDCertificateHash."

$MDCertificateHashNoSpaces = $MDCertificateHash.Replace(" ","")

Log "Looking for the local certificate."

$LocalCert = Get-ChildItem -Path "cert:\LocalMachine\My" | where {$_.Thumbprint -like $MDCertificateHashNoSpaces}
$Thumbprint = $LocalCert.Thumbprint

if($LocalCert.IssuerName.Name -like "*$LEName*"){

    Log "I found a certifcate from LetsEncrypt."
    $CertHostNames = $LocalCert.DnsNameList.Unicode
    $CertExpirationDate = $LocalCert.NotAfter
    #Log "Today is $MyDate"
    #Log "30 days from today is $($MyDate.AddDays(30))"
    if($CertExpirationDate -ge $MyDate.AddDays(30)) {
        Log "The certificate is still valid for 30 days."
        #Log "$CertExpirationDate is ge $($MyDate.AddDays(30))"
        if(!(CompareHostNames $HostNames $CertHostNames)) {

            Log "The list of host names has changed.  A new certificate will be requested."

            $NewCert = $true
        } elseif($ECDSA -and $LocalCert.SignatureAlgorithm.FriendlyName -notlike "*ECDSA") {
            Log "The local certificate is not using ECDSA, but ECDSA was requested."

            $NewCert = $true 
        } elseif($ECDSA -eq $false -and $LocalCert.SignatureAlgorithm.FriendlyName -notlike "*RSA"){
            Log "The local certificate is not using RSA, but RSA was requested."

            $NewCert = $true
        }
    } else {
        Log "The certificate is going to expire in the next 30 days, requesting a new certificate."
        $NewCert = $true
    }

} else {
    Log "The certificate is not from LetsEncrypt, requesting a new certificate."
    $NewCert = $true
}

#Log $NewCert
if($NewCert){

	#Checking the PSModulePath environment variable to see if the MDaemon module path is included.
	if($env:PSModulePath -notlike "*$LEModulePath*") {

		Log "Adding $LEModulePath to the PSModulePath Environment Variable for this session."
		$env:PSModulePath = $env:PSModulePath + ";$LEModulePath"    
	}

    Log "Importing the ACMESharp module."
    Import-Module ACME-PS -ErrorVariable LogText
    Log $LogText

    $State = UpdateState $StatePath
    
    #Checking to make sure the Service name matches the resource URLs. This prevents an issue when changing between the live system and the staging system.
    if($State.GetAccount().Status -eq "valid") {
        if(($ServiceName -eq "LetsEncrypt" -and $State.GetAccount().ResourceUrl -notlike "*acme-v02*") -or ($ServiceName -eq "LetsEncrypt-Staging" -and $State.GetAccount().ResourceUrl -notlike "*acme-staging-v02*")){
            
            Log "The Service Name does not match the Resource URL. Deleting the existing account and Creating a new account."
            
            Remove-Item $StatePath -Recurse -Force

            CreateNewAccount $StatePath $ServiceName $Email
        } 

    }

    #Check if the account already exists, if it does not, create the account.
    if($State.GetAccount().Status -ne "valid") {
        
        Log "The account either doesn't exist or is not valid. It will be deleted and recreated."
        if(Test-Path $StatePath) {
            Remove-Item $StatePath -Recurse -Force 
        }

        CreateNewAccount $StatePath $ServiceName $Email

    } else {
	
	    Log "The account is setup and the status is valid."
    }

    Write-Host "Getting another updated state, just in case."
    $State = UpdateState $StatePath

    GetServiceDirectory $State $ServiceName 

    GetNewNonce $State
    

    foreach($HName in $HostNames) {
        Log "Getting identifier for $HName."
        GetIdentifier $HName
	    Log $LogText
    }

    $Order = CreateOrder $StatePath $FQDN
    
    $State = UpdateState $StatePath

    GetServiceDirectory $State $ServiceName

    $Authorizations = GetAuthorizations $State $Order

    GetServiceDirectory $State $ServiceName

    $State = UpdateState $StatePath

    CompleteChallenges $State $Authorizations $Order

    GetCertKey $Order $CertKeyPath $State
    
    CompleteOrder $State $Order

    DownloadCertificate $State $Order $CertOutput

    Log "All done, there's a pfx file at $CertOutput."

    $Thumbprint = Get-CertificateThumbprint $CertOutput $CertPassword

	Log "The thumbprint of the new certificate is: $Thumbprint"

} else {
    Log "A new certificate is not being requested."
}

$Thumbprint = $Thumbprint -replace '(....(?!$))','$1 '

if($MDCertificateHash -ne $ThumbPrint)
{

    #Import the cert into Windows...
	#The extra quotes are so that $CertOutput path will be enclosed in quotes.
	$Arguments = "-f -p $CertPassword -importpfx ""$CertOutput"""

	Log "Importing the certificate."
	$ImportResults = Start-Process "certutil.exe" -ArgumentList $Arguments -Wait -PassThru -ErrorVariable LogText
	LogNoError $LogText

	if($ImportResults.ExitCode -ne 0)
	{
		Log "The certificate could not be imported.  Check the log at $LetsEncryptLog for more information.`r`nThe script will now stop." "Red"
		exit 1
	}

    #Configure MDaemon to use it and reload the settings.
    
	Log "Setting the certificate hash value in the MDaemon.ini file to $ThumbPrint."
	Write-PrivateProfileSTring $MDINIPath "SSL" "CertificateHash" $Thumbprint
	Write-PrivateProfileSTring $MDINIPath "SSL" "CertStoreLocation" "LocalMachine"
	Write-PrivateProfileSTring $MDINIPath "SSL" "CertStoreName" "My"

	#Setting this so that MDaemon will be restarted.
	$Restart = $true
}
else
{
	Log "MDaemon is already configured to use $Thumbprint as the certificate hash."
	Log "The MDaemon.ini file will not be updated."
}



if($WCRunUnderIIS -eq "Yes")
{
    Log "WorldClient is configured to run under IIS."
    
    if(CheckWebScriptingInstalled)
    {
        Log "The web scripting tools are installed."

        if($IISSiteName -eq "")
        {
            Log "Error: No IIS website name specified."
        }
        else
        {
            Log "Configuring the $IISSiteName website in IIS to use $Thumbprint."

            $binding = Get-WebBinding -Name $IISSiteName -Protocol https

            if(!($binding)){
                New-WebBinding -Name $IISSiteName -IPAddress * -Port 443 -Protocol "https"
                $binding = Get-WebBinding -Name $IISSiteName -Protocol https
            }
			$ThumbprintNoSpaces = $Thumbprint.replace(" ","")

            $binding.AddSslCertificate($ThumbprintNoSpaces, "my")

        }
    }
    else
    {
        Log "Error: Cannot configure the IIS website. The Microsoft Web Scripting Tools are not installed."
    }

}
else
{
	$WCCertificateHash = Get-PrivateProfileString $WCINIPath "SSL" "CertificateHash"
	if($WCCertificateHash -ne $ThumbPrint)
	{
		Log "Setting the certificate hash value in the $WCINIPath file to $ThumbPrint."

		Write-PrivateProfileString $WCINIPath "SSL" "CertificateHash" $Thumbprint
		Write-PrivateProfileString $WCINIPath "SSL" "CertStoreLocation" "LocalMachine"
		Write-PrivateProfileString $WCINIPath "SSL" "CertStoreName" "My"

		$Restart = $true
	}
	else
	{
		Log "WorldClient is already configured to use $Thumbprint as the certificate hash."
		Log "The WorldClient.ini file will not be updated."
	}
}

$WARunUnderIIS = Get-PrivateProfileString $WAINIPath "WebServer" "RunUnderIIS"

if($WARunUnderIIS -eq "Yes")
{
    Log "MDaemon Remote Administration is configured to run under IIS."
    #Check to see if web scripting tools are installed.

    if(CheckWebScriptingInstalled)
    {
        Log "The web scripting tools are installed."
        
        if($IISSiteName -eq "")
        {
            Log "Error: No IIS website name specified."
        }
        else
        {
            Log "Configuring the $IISSiteName website in IIS to use $Thumbprint."

            $binding = Get-WebBinding -Name $IISSiteName -Protocol https

            if(!($binding)){
                New-WebBinding -Name $IISSiteName -IPAddress * -Port 443 -Protocol "https"
                $binding = Get-WebBinding -Name $IISSiteName -Protocol https
            }
			$ThumbprintNoSpaces = $Thumbprint.replace(" ","")

            $binding.AddSslCertificate($ThumbprintNoSpaces, "my")

        }
    }
    else
    {
        Log "Error: Cannot configure the IIS website. The Microsoft Web Scripting Tools are not installed."
    }
}
else
{
	$WACertificateHash = Get-PrivateProfileString $WAINIPath "SSL" "CertificateHash"
	if($WACertificateHash -ne $ThumbPrint)
	{
		Log "Setting the certificate hash value in the $WAINIPath file to $ThumbPrint."

		Write-PrivateProfileSTring $WAINIPath "SSL" "CertificateHash" $Thumbprint
		Write-PrivateProfileSTring $WAINIPath "SSL" "CertStoreLocation" "LocalMachine"
		Write-PrivateProfileSTring $WAINIPath "SSL" "CertStoreName" "My"

		$Restart = $true
	}
	else
	{
		Log "MDaemon Remote Administration is already configured to use $Thumbprint as the certificate hash."
		Log "The WebAdmin.ini file will not be updated."
	}
}

if($Restart)
{
	Log "Stopping MDaemon..."
	if(Stop-ServiceWithTimeout MDaemon 120)
	{
		Log "The MDaemon service has stopped."
	}
	else
	{
		$Message = "The MDaemon service did not stop in a timely manner. MDaemon needs to be manually restarted."
		$MessageText = $EmailBody + "`r`n`r`n" + $Message
		Log  $Message "Red"
		Log "This is a critical error, the script will now stop." "Red"

		SendEmail $email $To $EmailSubject $MessageText 
	}

	$WAEnabled = Get-PrivateProfileString $MDINIPath "WebAdmin" "EnableWebAdmin"
	$StopWA = Get-PrivateProfileSTring $MDINIPath "WebAdmin" "StopWebAdminWithMDaemon"

	if($StopWA -ne "Yes" -and $WARunUnderIIS -ne "Yes" -and $WAEnabled -ne "No")
	{
		if(Stop-ServiceWithTimeout WebAdmin 120)
		{
			Log "The MDaemon Remote Administration service has stopped."
		}
		else
		{
			$Message = "The MDaemon Remote Administration service did not stop in a timely manner. MDaemon RemoteAdministration needs to be manually restarted."
			$MessageText = $EmailBody + "`r`n`r`n" + $Message
			Log  $Message "Red"
			Log "This is a critical error, the script will now stop." "Red"

			SendEmail $email $To $EmailSubject $MessageText 
		}
	}

	Log "Starting MDaemon..."
	Start-Service -Name MDaemon -ErrorVariable LogText
	Log $LogText

	if($StopWA -ne "Yes" -and $WARunUnderIIS -ne "Yes" -and $WAEnabled -ne "No")
	{
		Log "Starting MDaemon Remote Administration."
		Start-Service -Name WebAdmin -ErrorVariable LogText
		Log $LogText
	}
}
else
{
	Log "No INI files were updated. No restart is required."
	
}

Log "Cleaning up old files."

Log "Checking for PFX files that begin with $FQDN and are older than 180 days in the $PemPath directory."

Foreach($File in get-childitem -Path $PEMPath)
{
    if($File.Extension -eq ".pfx" -and $File.Name -like "FQDN*" -and $File.CreationTime -lt $MyDate.AddDays(-180))
    {
        Log "Removing $($File.FullName)"
        Remove-Item $File.FullName -Force
        $Error.Clear()
    }
}

Log "Checking for files older than 180 days in the $AcmeChallengePath directory."

Foreach($File in get-childitem -Path $AcmeChallengePath)
{
    if($File.CreationTime -lt $MyDate.AddDays(-180))
    {
       
        Log "Removing $($File.FullName)"
        Remove-Item $File.FullName -Force
        #Clearing errors so that if an error occurs at this point, no failure email will be sent.
        $Error.Clear()
    }
}

if($RemoveOldCertificates) {

	Log "Checking for certificates that expired more than 30 days ago"

	foreach($CertToDelete in Get-ChildItem -Path Cert:\LocalMachine\My -DNSName "*$FQDN*" | Where-Object {($_.Issuer -like "*Let's Encrypt*" -or $_.Issuer -eq "CN=Fake LE Intermediate X1") -and $_.NotAfter -lt (get-date).AddDays(-30)}) {

		$CertPath = "Cert:\LocalMachine\My\$($CertToDelete.Thumbprint)"

		Log "Removing a certificate from $CertPath"
		Remove-Item -Path $CertPath
        #Clearing errors so that if an error occurs at this point, no failure email will be sent.
        $Error.Clear()
	}
    #This will clear errors generated by Get-ChildItem in the ForEach loop.
    $Error.Clear()
} 

Log "The script run is complete."
#Adding a blank line so its easier to see the start of a new session in the log
Log " "
Exit 0

'''
It produced this output:

"identifier": {
        "type": "dns",
        "value": "mail.primeshipping.ru"
    },
    "status": "invalid",
    "expires": "2024-04-09T04:11:30Z",
    "challenges": [
        {
            "type": "http-01",
            "status": "invalid",
            "error": {
                "type": "urn:ietf:params:acme:error:unauthorized",
                "detail": "87.229.180.186: Invalid response from http://mail.primeshipping.ru/.well-known/acme-challenge/cMoNsLCZMh66nCieI3gcgpep9Xk991OYjjD58Hv_PFM: 404",
                "status": 403
            },
            "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/333526403267/HvUV_g",
            "token": "cMoNsLCZMh66nCieI3gcgpep9Xk991OYjjD58Hv_PFM",
            "validationRecord": [
                {
                    "url": "http://mail.primeshipping.ru/.well-known/acme-challenge/cMoNsLCZMh66nCieI3gcgpep9Xk991OYjjD58Hv_PFM",
                    "hostname": "mail.primeshipping.ru",
                    "port": "80",
                    "addressesResolved": [
                        "87.229.180.186",
                        "88.200.208.18"
                    ],
                    "addressUsed": "87.229.180.186",
                    "resolverAddrs": [
                        "A:10.1.12.89:26534",
                        "AAAA:10.1.12.81:31390"
                    ]
                }
            ],
            "validated": "2024-04-02T04:11:35Z"
        }

My web server is (include version):

The operating system my web server runs on is (include version):

My hosting provider, if applicable, is:

I can login to a root shell on my machine (yes or no, or I don't know):

I'm using a control panel to manage my site (no, or provide the name and version of the control panel):

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot):

Hi, please put three backticks ``` before and after your script code block to make your post readable.

3 Likes

backticked it - lol

4 Likes

Hi @Hirurg163, and welcome to the LE community forum :slight_smile:

I see two IPs.
Is that expected?

5 Likes

image
The certificates installed by R3 are expired, the mail does not work with it. Issued a self-signed one. The error does not change under R3

Yes, primary and backup

How did you get the last LE cert [on both servers]?

3 Likes

Yes, MDaemon updated them automatically.

I don't see how it could; Being that each cert required at least three validations.
That was a one in eight chance of success [with two IPs per name].
Today, four validations are required.
That's now only a one in sixteen chance of success [with two IPs per name].

I suggest you devise a new plan of attack.
Like:

  • use individual email server names [one for each IP]
  • use DNS-01 authentication [easier said than done]
  • forward all challenge requests to one of the mail servers ("cert manager") [via alternate FQDN]
    [obtain the cert on the "cert manager" and then copy/rsync the cert files to the other mail server]
2 Likes

This action did not bring any result.

{
"identifier": {
"type": "dns",
"value": "mail.primeshipping.ru"
},
"status": "invalid",
"expires": "2024-04-09T07:34:43Z",
"challenges": [
{
"type": "http-01",
"status": "invalid",
"error": {
"type": "urn:ietf:params:acme:error:unauthorized",
"detail": "87.229.180.186: Invalid response from http://mail.primeshipping.ru/.well-known/acme-challenge/YvYhihhdIXtBt4lzf8qbYjbEnauIELQGUOQvyMwKtP8: 404",
"status": 403
},
"url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/333583394817/RB7scw",
"token": "YvYhihhdIXtBt4lzf8qbYjbEnauIELQGUOQvyMwKtP8",
"validationRecord": [
{
"url": "http://mail.primeshipping.ru/.well-known/acme-challenge/YvYhihhdIXtBt4lzf8qbYjbEnauIELQGUOQvyMwKtP8",
"hostname": "mail.primeshipping.ru",
"port": "80",
"addressesResolved": [
"87.229.180.186"
],
"addressUsed": "87.229.180.186",
"resolverAddrs": [
"A:10.1.12.81:31390",
"AAAA:10.1.12.81:31390"
]
}
],
"validated": "2024-04-02T07:34:46Z"
}
]
}

Is that the entire log?

1 Like

Unable to send an error email. No To address was specified.
Starting Script run at 04/02/2024 12:39:42.
Get the MDaemon paths.
The MDaemon.ini Path is E:\MDaemon\APP\MDaemon.ini.
The MDaemon APP Path is E:\MDaemon\APP.
The MDaemon Pem path is E:\MDaemon\PEM.
The MDaemon Log path is E:\MDaemon\Logs.
The MDaemon RAW path is E:\MDaemon\Queues\Raw.
The WorldClient Path is E:\MDaemon\WorldClient.
The WorldClient HTML Path is E:\MDaemon\WorldClient\HTML.
The well-known path is E:\MDaemon\WorldClient\HTML.well-known.
The Acme-Challenge path is E:\MDaemon\WorldClient\HTML.well-known\Acme-challenge.
The State Path is E:\MDaemon\PEM_LEState.
The FQDN is set to mail.primeshipping.ru.
The email address is set to postmaster@mail.primeshipping.ru.
Setting the system to use the LetsEncrypt Live Service.
The certificate thumbrpint in the MDaemon.ini file is C1AD 8D26 3F39 C6D2 8654 ECD8 AC7D 2104 FAF2 F426.
Looking for the local certificate.
The certificate is not from LetsEncrypt, requesting a new certificate.
Importing the ACMESharp module.
Getting an updated state.
The account is setup and the status is valid.
Getting an updated state.
Getting service directory.
Getting a new Nonce
Getting identifier for mail.primeshipping.ru.
Getting identifier for mail.primeshipping.ru.
Creating new certificate.
Creating a new order for mail.primeshipping.ru using dns:mail.primeshipping.ru
Getting an updated state.
Getting service directory.
Getting an authorization for the dns:mail.primeshipping.ru.
Getting service directory.
Getting an updated state.
The .well-known path for is E:\MDaemon\WorldClient\HTML.well-known
The Acme Challenge path for E:\MDaemon\WorldClient\HTML.well-known\Acme-challenge
Selecting the http-01 challenge and getting challenge data for dns:mail.primeshipping.ru.
The challenge status URL is https://acme-v02.api.letsencrypt.org/acme/chall-v3/333601344727/RR076w.
The challenge identifier is dns:mail.primeshipping.ru.
The URL to verify the challenge is mail.primeshipping.ru/.well-known/acme-challenge/O84Y4cGiIhVMf32twLm9sle9kjHwEScThOX48ZDB_kE.
The Challenge file name for dns:mail.primeshipping.ru is O84Y4cGiIhVMf32twLm9sle9kjHwEScThOX48ZDB_kE
The Challenge Content for dns:mail.primeshipping.ru is O84Y4cGiIhVMf32twLm9sle9kjHwEScThOX48ZDB_kE.Xi8qRvjO4r31f5cNGYxUmW0xpVDFBQvpTkV4A6Na4UE
Creating E:\MDaemon\WorldClient\HTML.well-known\Acme-challenge\O84Y4cGiIhVMf32twLm9sle9kjHwEScThOX48ZDB_kE for dns:mail.primeshipping.ru.
Submitting the ACME challenge for dns:mail.primeshipping.ru for verification.
Waiting for the order status to update... 0
Error: The challenge did not complete.

            Host Name: mail.primeshipping.ru
            Error Code: 403
            Error Type: urn:ietf:params:acme:error:unauthorized
            Error Detail: 87.229.180.186: Invalid response from http://mail.primeshipping.ru/.well-known/acme-challenge/O84Y4cGiIhVMf32twLm9sle9kjHwEScThOX48ZDB_kE: 404

This is a critical error, the script will now stop.
Information obtained from the following URLs: https://acme-v02.api.letsencrypt.org/acme/authz-v3/333601344727

Has there been any change on that system?
Is IIS [or any other web service] installed/running?
Is that the IP of that system [87.229.180.186]?

1 Like

There is an old Apache system replying to HTTP requests to that domain.

Can you show output of below?

apachectl -t -D DUMP_VHOSTS

The '404' error means the Let's Encrypt Server requested the challenge token but your server said the token was Not Found. Not the 404 below that is just a test. The 404 that appears in the cert request error message.

curl -i http://mail.primeshipping.ru/.well-known/acme-challenge/Test404
HTTP/1.1 404 Not Found
Server: Apache/2.2.3 (Win32) PHP/5.3.5 mod_perl/2.0.3-dev Perl/v5.8.8
3 Likes

There were no changes, 2 months ago everything was updated automatically.

You are currently using HTTP validation to prove you control your domain to the Certificate Authority (Let's Encrypt). If you load balance between two servers then they will give different HTTP responses because only one will be running your script, unless the servers they are running know to act cooperatively between the two instances (to give the same replies).

You may be having less luck due to Let's Encrypt recently switching to using more validation "perspectives" (checking your domain from multiple global locations and ensuring they agree). Your servers need to reply with the same response to the HTTP challenge. I would suggest contacting MDaemon support because all of their customers with multiple servers will be affected.

1 Like

Thank you very much, I have already applied. I will inform you about the results based on the response from the support service.

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.