Posted On December 9, 2020

PowerShell: Process Watcher – Perform CRM Async Services Maintenance

kimconnect 0 comments
blog.KimConnect.com >> Codes >> PowerShell: Process Watcher – Perform CRM Async Services Maintenance

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

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post

PowerShell: Automating Dynamics CRM Migration

This is the source code that has been used to move hundreds of Dynamics CRM…

Script to Disable User Accounts

accountsToDisable.txtorangeapplepeartermed user REM disableAccounts.batREM read accountsToDisable.txt and convert names into proper DN entries. Save result…

PowerShell: Get Size On Disk and Discover Largest Files in a Given Directory

# getSizeOnDisk-v0.01.ps1# Set variables$parentDirectory="C:\"$depth=4$topX=10$excludeDirectories="C:\Windows","C:\Program Files","C:\Program Files (x86)"$clusterSizeOfNetworkShare=8192# sanitateif($depth -le 1){$depth=1}################################## Excuting Program as an Administrator…
Other project: DragonCoin.com