Update: this script is deprecated in favor of a better one here.

Version 1:

# invokeCrmAsyncMaintenance.ps1
# Version 0.0.1
# This script is a processes watcher on CRM Servers
# It ensures that all automatically starting services are running
# Moreover, there's a special handling of the service name 'MSCRMAsyncService$maintenance'

# Obtain credentials stored in environment variables
$username=$env:username
$password=$env:password
$encryptedPassword=ConvertTo-SecureString $password -AsPlainText -Force
$credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName,$encryptedPassword;

# List of servers to check
$computerNames=@('CRM1-ASYNC','CRM2-ASYNC')

# Email relay parameters
$emailFrom='[email protected]'
$emailTo='[email protected]'
$subject='Async Server Issues'
$smtpRelayServer='relay.kimconnect.com'

function invokeCrmAsyncMaintenance{
    param(
        [string[]]$computerNames=$env:computername,
        [pscredential]$credentials,
        [string[]]$skipServices=@('gupdate','MapsBroker','RemoteRegistry','sppsvc','WbioSrvc','twdservice','MSCRMAsyncService$maintenance'),
        [string]$crmAsyncServiceName='MSCRMAsyncService$maintenance',
        [string]$desiredStatus='running'
    )
    $results=@()
    function startAllAutoServices{
        param(
            [string[]]$skipServices=@('gupdate','MapsBroker','RemoteRegistry','sppsvc','WbioSrvc')
            )        
        $filterString=($skipServices|%{"AND name!='$_' "}) -join ''
        $stoppedServices=Get-WmiObject win32_service -Filter "startmode='auto' AND state!='running' $filterString"
        if($stoppedServices){
            write-warning "$env:computername`: these services were not running $($stoppedServices.Name).`r`nProgram now attempts to start them."
            $null=$stoppedServices|Invoke-WmiMethod -Name StartService
            (get-service $stoppedServices.Name).waitforstatus('Running')
            # Detect failed services
            $failedServices=Get-WmiObject win32_service -Filter "startmode = 'auto' AND state != 'running' $filterString"
            if($failedServices){
                write-warning "$env:computername`: failed to start these services $($failedServices.Name)"
            }else{
                write-host "$env:computername`: all auto-start services are running." -ForegroundColor Green
            }
            return [pscustomobject]@{
                stoppedServices=($stoppedServices.Name|out-string).trim()
                failedToStartServices=if($failedServices){($failedServices.Name|out-string).trim()}else{'none'}
            }
        }else{
            write-host "$env:computername`: all auto-start services are running." -ForegroundColor Green
            return [pscustomobject]@{
                stoppedServices='none'
                failedToStartServices='none'
            }
        }        
    }    
    function checkService($serviceName,$status='Running'){
        # Sanitation
        $systemInvalidChars =[Regex]::Escape(-join [System.Io.Path]::GetInvalidFileNameChars())
        $regexInvalidChars = "[$systemInvalidChars]"
        $serviceName=$serviceName -replace [regex]::Matches($serviceName, $regexInvalidChars, 'IgnoreCase').Value
        try{
            $service=get-service -name $serviceName|select -first 1
            if($service.Status -eq $status){
                write-host "$serviceName status of $status is matching the desired state." -foregroundcolor Green
                return 0
            }elseif($null -ne $service.Status){
                write-host "$env:computername`: $serviceName status $($service.Status) doesn`'t match the desired state" -foregroundcolor Red
                return 1
            }else{
                write-host "$serviceName was not found" -foregroundcolor Red
                return -1          
            }
        }catch{
            Write-Error $_
            write-host "$serviceName was not found" -foregroundcolor Red
            return -1
        }
    }

    $fixAsyncService={
        $erroractionpreference='stop'
        try{
            $crmTools=if(test-path "$env:programfiles\dynamics 365"){"$env:programfiles\dynamics 365\Tools"
                }else{"$env:programfiles\Microsoft Dynamics CRM\Tools"}
            write-host 'Restarting Microsoft Dynamics CRM Asynchronous Services...'
            get-service|?{$_.name -like 'MSCRMAsyncService*'}|restart-service
            write-host 'Renewing keys to CRM App server...'
            & "$crmTools\Microsoft.Crm.Tools.WRPCKeyRenewal.exe" /R
            if($LASTEXITCODE -ne 0){
                write-host 'Microsoft.Crm.Tools.WRPCKeyRenewal.exe command failed.'
                $result=$false
            }else{
                sleep 3
                write-host 'Restarting Microsoft Dynamics CRM Asynchronous Service (maintenance)...'
                get-service|?{$_.DisplayName -like 'Microsoft Dynamics*(maintenance)'}|restart-service
                $result=$true
                }
            return $result
        }catch{
            write-host $_
            return $false
        }
    }

    foreach ($computerName in $computerNames){
        $session=if($credentials){
                new-pssession -ComputerName $computerName -Credential $credentials
            }else{
                new-pssession -ComputerName $computerName
            }
        if($session.state -eq 'Opened'){
            $serviceStatusCode=invoke-command -Session $session -ScriptBlock{
                param ($checkService,$crmAsyncServiceName)
                [scriptblock]::create($checkService).invoke($crmAsyncServiceName)
                } -args ${function:checkService},$crmAsyncServiceName
            $asyncServiceResult=if ($serviceStatusCode -eq 0){
                    write-host "$crmAsyncServiceName is healthy on $computerName. No actions required." -foregroundcolor Green
                    $true
                }elseif($serviceStatusCode -eq 1){
                    invoke-command -session $session -scriptblock $fixAsyncService
                }else{
                    write-host "There appears to be a problem with the service status code of $serviceStatusCode"
                    $false
                }
            $otherServicesResult=invoke-command -Session $session -ScriptBlock{
                param ($startAllAutoServices,$skipServices)
                [scriptblock]::create($startAllAutoServices).invoke($skipServices)
                } -args ${function:startAllAutoServices},$skipServices
            Remove-PSSession $session        
            $results+=[pscustomobject]@{
                computername=$computername
                asyncServiceIsRunning=$asyncServiceResult
                stoppedServices=$otherServicesResult.stoppedServices
                failedToStartServices=$otherServicesResult.failedToStartServices
                }
        }else{
            write-warning "$env:computername cannnot connect to $computername via WinRM"
            $results+=[pscustomobject]@{
                computername=$computername
                asyncServiceIsRunning='Unknown'
                stoppedServices='Unknown'
                failedToStartServices='Unknown'
                }
        }
    }
    return $results    
}

$results=invokeCrmAsyncMaintenance $computerNames $credentials
$problemsDetected=$results|?{$_.stoppedServices -notmatch 'none'}
if($problemsDetected){
    Send-MailMessage -From $emailFrom `
    -To $emailTo `
    -Subject $subject `
    -Body $($problemsDetected|convertto-html -Fragment|out-string) `
    -BodyAsHtml `
    -SmtpServer $smtpRelayServer
}

Previous versions:

# crmAsyncMaintenance.ps1
# This script is a processes watcher on CRM Servers
# It ensures that all automatically starting services are running
# Moreover, there's a special handling of the service name 'MSCRMAsyncService$maintenance'

$computerNames='CRM01','CRM02'

function performCrmAsyncMaintenance{
    param(
        [string[]]$computerNames=$env:computername,
        [string[]]$skipServices=@('gupdate','MapsBroker','RemoteRegistry','sppsvc','WbioSrvc','twdservice','MSCRMAsyncService$maintenance'),
        [string]$crmAsyncServiceName='MSCRMAsyncService$maintenance',
        [string]$desiredStatus='running'
    )
    $results=@()

    function startAllAutoServices{
        param(
            [string[]]$computerNames=$env:computername,
            [string[]]$skipServices=@('gupdate','MapsBroker','RemoteRegistry','sppsvc','WbioSrvc')
            )
        
        $results=@()
        foreach ($computerName in $computerNames){
            # Start all non-running Auto-Start services
            # $exceptions=($skipServices|%{"'$_'"}) -join ','
            $filterString=($skipServices|%{"AND name!='$_' "}) -join ''
            $nonRunningServices=Get-WmiObject win32_service -ComputerName $computerName -Filter "startmode='auto' AND state!='running' $filterString"
            if($nonRunningServices){
                $nonRunningServices|Invoke-WmiMethod -Name StartService
                # Detect non-running services
                $failedServices = Get-WmiObject win32_service -ComputerName $computerName -Filter "startmode = 'auto' AND state != 'running' $filterString" | select -expand Name
                if($failedServices){
                    write-warning "$computerName`: services with 'stopped' status $failedServices"
                }else{
                    write-host "$computerName`: all auto-start services are running." -ForegroundColor Green
                }
                $results+=[pscustomobject]@{
                    computername=$computerName
                    nonRunningServices=($nonRunningServices.Name|out-string).trim()
                    failedToStart=($failedServices|out-string).trim()
                }
            }else{
                write-host "$computerName all auto-start services are running." -ForegroundColor Green
                $results+=[pscustomobject]@{
                    computername=$computerName
                    nonRunningServices='none'
                    failedToStart='none'
                }
            }
        }
        return $results
    }    
    function checkService($computername=$env:computername,$serviceName,$status='Running'){
    # Sanitation
    #$invalidChars=$processName.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars())
    $systemInvalidChars =[Regex]::Escape(-join [System.Io.Path]::GetInvalidFileNameChars())
    $regexInvalidChars = "[$systemInvalidChars]"
    $serviceName=$serviceName -replace [regex]::Matches($serviceName, $regexInvalidChars, 'IgnoreCase').Value
    if($desiredStatus -ne 'Stopped'){
        $desiredStatus='Running'
    }
    try{
        $service=get-service -name $serviceName -ComputerName $computername|select -first 1
        if($service.Status -eq $status){
            write-host "$serviceName status of $status is matching the desired state." -foregroundcolor Green
            return 0
        }elseif($null -ne $service.Status){
            write-host "status doesn`'t match the desired state" -foregroundcolor Red
            return 1
        }else{
            write-host "$serviceName was not found" -foregroundcolor Red
            return -1          
        }
    }catch{
        Write-Error $_
        write-host "$serviceName was not found" -foregroundcolor Red
        return -1
    }
    }

    $action={
        $erroractionpreference='stop'
        try{
            $crmTools=if(test-path "$env:programfiles\dynamics 365"){"$env:programfiles\dynamics 365\Tools"
                }else{"$env:programfiles\Microsoft Dynamics CRM\Tools"}
            write-host 'Restarting Microsoft Dynamics CRM Asynchronous Services...'
            get-service|?{$_.name -like 'MSCRMAsyncService*'}|restart-service
            write-host 'Renewing keys to CRM App server...'
            & "$crmTools\Microsoft.Crm.Tools.WRPCKeyRenewal.exe" /R
            if($LASTEXITCODE -ne 0){
                write-host 'Microsoft.Crm.Tools.WRPCKeyRenewal.exe command failed.'
                $result=$false
            }else{
                sleep 3
                write-host 'Restarting Microsoft Dynamics CRM Asynchronous Service (maintenance)...'
                get-service|?{$_.DisplayName -like 'Microsoft Dynamics*(maintenance)'}|restart-service
                $result=$true
                }
            return $result
        }catch{
            write-host $_
            return $false
        }
    }
    function performAction($computerName,$serviceStatusCode,$action){
        if ($serviceStatusCode -eq 0){
            write-host "$serviceName is healthy. No actions required." -foregroundcolor Green
            return $true
        }elseif($serviceStatusCode -eq 1){
            $success=invoke-command -ComputerName $computerName -scriptblock $action
            return $success
        }else{
            write-host "There appears to be a problem with the service status code of $serviceStatusCode"
            return $false
        }
    }

    foreach ($computerName in $computerNames){
        $serviceStatusCode=checkService $computerName $crmAsyncServiceName $desiredStatus
        $asyncServiceResult=if($serviceStatusCode -gt 0){performAction $computerName $serviceStatusCode $action}elseif($serviceStatusCode -eq 1){'Error: service not found'}else{$true}
        $otherServicesResult=(startAllAutoServices $computerName $skipServices).failedToStart
        $results+=[pscustomobject]@{
            computername=$computername
            asyncServiceIsRunning=$asyncServiceResult
            failedAutorunServices=$otherServicesResult
            }
    }
    return $results
}

performCrmAsyncMaintenance $computerNames