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):