Install-Dynamics365Server.psm1

function Install-Dynamics365Server {
    [CmdletBinding(DefaultParameterSetName = 'Groups')]
    param (
        [Parameter(ParameterSetName = 'OU', Mandatory=$true)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$true)]
        [string]
        $MediaDir,
        [Parameter(ParameterSetName = 'OU', Mandatory=$true)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$true)]
        [string]
        $SqlServer,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $Patch,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $LicenseKey,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $CreateDatabase,
        [ValidateSet(
            "FrontEnd",
            "BackEnd",
            "DeploymentAdministration",
            "WebApplicationServer",
            "OrganizationWebService",
            "DiscoveryWebService",
            "HelpServer",
            "AsynchronousProcessingService",
            "EmailConnector",
            "SandboxProcessingService",
            "DeploymentTools",
            "DeploymentWebService",
            "VSSWriter"
        )]
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string[]]
        $ServerRoles,
        [Parameter(ParameterSetName = 'OU', Mandatory=$true)]
        [string]
        $OU,
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $PrivUserGroup,
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $SQLAccessGroup,
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $UserGroup,
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $ReportingGroup,
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $PrivReportingGroup,
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $AutoGroupManagementOff,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [pscredential]
        $CrmServiceAccount,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [pscredential]
        $DeploymentServiceAccount,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [pscredential]
        $SandboxServiceAccount,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [pscredential]
        $VSSWriterServiceAccount,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [pscredential]
        $AsyncServiceAccount,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [pscredential]
        $MonitoringServiceAccount,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $CreateWebSite,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [int]
        $WebSitePort,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $WebSiteUrl,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $Organization,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $OrganizationUniqueName,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $ReportingUrl,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $InstallDir,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $IncomingExchangeServer,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $BaseISOCurrencyCode,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $BaseCurrencyName,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $BaseCurrencySymbol,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [int]
        $BaseCurrencyPrecision,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $OrganizationCollation,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $SQM = $false,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $MUOptin = $false,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $Reboot = $false,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [string]
        $LogFilePath = $null,
        [ValidateRange(1,3600)]
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [int]
        $LogFilePullIntervalInSeconds = 30,
        [Parameter(ParameterSetName = 'OU', Mandatory=$false)]
        [Parameter(ParameterSetName = 'Groups', Mandatory=$false)]
        [switch]
        $LogFilePullToOutput = $false
    )
    if ( $ServerRoles ) {
        $serverRoleModel = @{
            FrontEnd = (
                "WebApplicationServer",
                "OrganizationWebService",
                "DiscoveryWebService",
                "HelpServer"
            )
            BackEnd = @(
                "AsynchronousProcessingService",
                "EmailConnector",
                "SandboxProcessingService"
            )
            DeploymentAdministration = @(
                "DeploymentTools",
                "DeploymentWebService",
                "VSSWriter"
            )
        }
        $refinedRoles = $ServerRoles | ForEach-Object {
            $roleOrGroupName = $_;
            switch ( $roleOrGroupName ) {
                "FrontEnd" {
                    Write-Output "WebApplicationServer";
                    Write-Output "OrganizationWebService";
                    Write-Output "DiscoveryWebService";
                    Write-Output "HelpServer";
                    break;
                }
                "BackEnd" {
                    Write-Output "AsynchronousProcessingService";
                    Write-Output "EmailConnector";
                    Write-Output "SandboxProcessingService";
                    break;
                }
                "DeploymentAdministration" {
                    Write-Output "DeploymentTools";
                    Write-Output "DeploymentWebService";
                    Write-Output "VSSWriter";
                    break;
                }
                default {
                    Write-Output $roleOrGroupName;
                    break;
                }
            }
        } | Select-Object -Unique;
        $ServerRoles = $refinedRoles;
        Write-Output "Refined server roles:";
        Write-Output $ServerRoles;
    } else {
        Write-Output "Specific roles are not requested, so full server will be installed";
    }
    $setupFilePath = "$mediaDir\SetupServer.exe";
    if ( Test-Path $setupFilePath ) {
        $fileVersion = [version]( Get-Command $setupFilePath ).FileVersionInfo.FileVersion;
        Write-Output "Version of software to be installed: $($fileVersion.ToString())";
        if ( !$LicenseKey ) {
            if ( $fileVersion.Major -eq 8 ) {
                $LicenseKey = "WCPQN-33442-VH2RQ-M4RKF-GXYH4";
            }
            if ( $fileVersion.Major -eq 9 ) {
                $LicenseKey = "KKNV2-4YYK8-D8HWD-GDRMW-29YTW";
            }
            if ( !$LicenseKey ) {
                $errorMessage = "License key is not specified and cannot be calculated";
                Write-Output $errorMessage;
                Throw $errorMessage;
            }
        }
        if ( Test-Path "$mediaDir\LangPacks" ) {
            $languagePacks = Get-ChildItem "$mediaDir\LangPacks";
            if ( $languagePacks -and $languagePacks.Count -eq 1 ) {
                $fileLanguageCode = [int]$languagePacks.Name;
                Write-Output "Language code of software to be installed: $fileLanguageCode";
                $msCRMRegistryValues = Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MSCRM -ErrorAction Ignore;
                if ( $msCRMRegistryValues ) {
                    $isInstalled = $true;
                    Write-Output "Some version of Dynamics is already installed on this machine"
                    $installedVersion = Get-Dynamics365ServerVersion;
                    if ( $installedVersion.Major -ne $fileVersion.Major ) {
                        $errorMessage = "Another version is already installed: $($installedVersion.ToString())";
                        Write-Output $errorMessage;
                        Throw $errorMessage;
                    }
                    $installedLanguage = Get-Dynamics365ServerLanguage;
                    if ( $installedLanguage -ne $fileLanguageCode ) {
                        $errorMessage = "Another language is already installed: $installedLanguage";
                        Write-Output $errorMessage;
                        Throw $errorMessage;
                    }
                    $installedRoles = Get-Dynamics365ServerRole;
                    Write-Output "Found Roles: ";
                    $installedRoles | Write-Output;
                    if ( $ServerRoles ) {
                        $ServerRoles | ForEach-Object {
                            if ( !( $installedRoles -contains $_ -or ( $_ -eq "VssWriter" -and $installedRoles -contains "VssWriterService" ) ) ) {
                                Write-Output "The $_ role is missing, installation will be done"
                                $isInstalled = $false;
                                $installType = "Configure";
                            }
                        }
                    }
                } else {
                    $isInstalled = $false;
                }
                if ( !$isInstalled ) {
                    if ( $Patch ) {
                        $resourcePathItem = Get-Item $Patch;
                        if ( !$resourcePathItem ) { throw "Specified file or directory '$Patch' could not be found" }
                        if ( $resourcePathItem.PSIsContainer ) {
                            $patchFiles = Get-Item $Patch\Server_KB*_amd64_$fileLanguageCode.msp
                            if ( $patchFiles ) {
                                $patchFilePath = $patchFiles[0].FullName;
                                Write-Output "Found patch file $patchFilePath"
                            } else {
                                throw "File could not be found: $Patch\Server_KB*_amd64_$fileLanguageCode.msp"
                            }
                        } else {
                            $patchFilePath = $Patch;
                        }
                    }
                    $xml = [xml]"";
                    $crmSetupElement = $xml.CreateElement( "CRMSetup" );
                        $serverElement = $xml.CreateElement( "Server" );
                            if ( $installType ) {
                                $installTypeElement = $xml.CreateElement( "InstallType" );
                                    $installTypeElement.InnerText = $installType;
                                $serverElement.AppendChild( $installTypeElement ) | Out-Null;
                            }
                            #Patch is a required element for setup
                            $patchElement = $xml.CreateElement( "Patch" );
                                if ( $null -ne $Patch ) {
                                    $patchElement.SetAttribute( "Update", $true ) | Out-Null;
                                    $patchElement.InnerText = $patchFilePath;
                                } else {
                                    $patchElement.SetAttribute( "Update", $false ) | Out-Null;
                                }
                            $serverElement.AppendChild( $patchElement ) | Out-Null;
                            $licenseKeyElement = $xml.CreateElement( "LicenseKey" );
                                $licenseKeyElement.InnerText = $licenseKey;
                            $serverElement.AppendChild( $licenseKeyElement ) | Out-Null;
                            if ( $installDir ) {
                                $installDirElement = $xml.CreateElement( "InstallDir" );
                                    $installDirElement.InnerText = $installDir;
                                $serverElement.AppendChild( $installDirElement ) | Out-Null;
                            }
                            $databaseElement = $xml.CreateElement( "Database" );
                                $databaseElement.SetAttribute( "create", $CreateDatabase ) | Out-Null;
                            $serverElement.AppendChild( $databaseElement ) | Out-Null;
                            $SQLServerElement = $xml.CreateElement( "SqlServer" );
                                $SQLServerElement.InnerText = $SQLServer;
                            $serverElement.AppendChild( $SQLServerElement ) | Out-Null;
                            if ( $OU) {
                                $OUElement = $xml.CreateElement( "OU" );
                                    $OUElement.InnerText = $OU;
                                $serverElement.AppendChild( $OUElement ) | Out-Null;
                            }
                            if ( $privUserGroup -or $SQLAccessGroup -or $userGroup -or $reportingGroup -or $privReportingGroup -or $AutoGroupManagementOff ) {
                                $groupsElement = $xml.CreateElement( "Groups" );
                                    $groupsElement.SetAttribute( "autogroupmanagementoff", $AutoGroupManagementOff ) | Out-Null;
                                    if ( $privUserGroup ) {
                                        $privUserGroupElement = $xml.CreateElement( "PrivUserGroup" );
                                            $privUserGroupElement.InnerText = $privUserGroup;
                                        $groupsElement.AppendChild( $privUserGroupElement ) | Out-Null;
                                    }
                                    if ( $SQLAccessGroup ) {
                                        $SQLAccessGroupElement = $xml.CreateElement( "SQLAccessGroup" );
                                            $SQLAccessGroupElement.InnerText = $SQLAccessGroup;
                                        $groupsElement.AppendChild( $SQLAccessGroupElement ) | Out-Null;
                                    }
                                    if ( $userGroup ) {
                                        $userGroupElement = $xml.CreateElement( "UserGroup" );
                                            $userGroupElement.InnerText = $userGroup;
                                        $groupsElement.AppendChild( $userGroupElement ) | Out-Null;
                                    }
                                    if ( $reportingGroup ) {
                                        $reportingGroupElement = $xml.CreateElement( "ReportingGroup" );
                                            $reportingGroupElement.InnerText = $reportingGroup;
                                        $groupsElement.AppendChild( $reportingGroupElement ) | Out-Null;
                                    }
                                    if ( $privReportingGroup ) {
                                        $privReportingGroupElement = $xml.CreateElement( "PrivReportingGroup" );
                                            $privReportingGroupElement.InnerText = $privReportingGroup;
                                        $groupsElement.AppendChild( $privReportingGroupElement ) | Out-Null;
                                    }
                                $serverElement.AppendChild( $groupsElement ) | Out-Null;
                            }
                            if ( $CRMServiceAccount ) {
                                $CRMServiceAccountElement = $xml.CreateElement( "CrmServiceAccount" );
                                    $CRMServiceAccountElement.SetAttribute( "type", "DomainUser" ) | Out-Null;
                                    $serviceAccountLoginElement = $xml.CreateElement( "ServiceAccountLogin" );
                                        $serviceAccountLoginElement.InnerText = $CRMServiceAccount.UserName;
                                    $CRMServiceAccountElement.AppendChild( $serviceAccountLoginElement ) | Out-Null;
                                    $serviceAccountPasswordElement = $xml.CreateElement( "ServiceAccountPassword" );
                                        $serviceAccountPasswordElement.InnerText = $CRMServiceAccount.GetNetworkCredential().Password;
                                    $CRMServiceAccountElement.AppendChild( $serviceAccountPasswordElement ) | Out-Null;
                                $serverElement.AppendChild( $CRMServiceAccountElement ) | Out-Null;
                            }
                            if ( $deploymentServiceAccount ) {
                                $deploymentServiceAccountElement = $xml.CreateElement( "DeploymentServiceAccount" );
                                    $deploymentServiceAccountElement.SetAttribute( "type", "DomainUser" ) | Out-Null;
                                    $serviceAccountLoginElement = $xml.CreateElement( "ServiceAccountLogin" );
                                        $serviceAccountLoginElement.InnerText = $deploymentServiceAccount.UserName;
                                    $deploymentServiceAccountElement.AppendChild( $serviceAccountLoginElement ) | Out-Null;
                                    $serviceAccountPasswordElement = $xml.CreateElement( "ServiceAccountPassword" );
                                        $serviceAccountPasswordElement.InnerText = $deploymentServiceAccount.GetNetworkCredential().Password;
                                    $deploymentServiceAccountElement.AppendChild( $serviceAccountPasswordElement ) | Out-Null;
                                $serverElement.AppendChild( $deploymentServiceAccountElement ) | Out-Null;
                            }
                            if ( $sandboxServiceAccount ) {
                                $sandboxServiceAccountElement = $xml.CreateElement( "SandboxServiceAccount" );
                                    $sandboxServiceAccountElement.SetAttribute( "type", "DomainUser" ) | Out-Null;
                                    $serviceAccountLoginElement = $xml.CreateElement( "ServiceAccountLogin" );
                                        $serviceAccountLoginElement.InnerText = $sandboxServiceAccount.UserName;
                                    $sandboxServiceAccountElement.AppendChild( $serviceAccountLoginElement ) | Out-Null;
                                    $serviceAccountPasswordElement = $xml.CreateElement( "ServiceAccountPassword" );
                                        $serviceAccountPasswordElement.InnerText = $sandboxServiceAccount.GetNetworkCredential().Password;
                                    $sandboxServiceAccountElement.AppendChild( $serviceAccountPasswordElement ) | Out-Null;
                                $serverElement.AppendChild( $sandboxServiceAccountElement ) | Out-Null;
                            }
                            if ( $VSSWriterServiceAccount ) {
                                $VSSWriterServiceAccountElement = $xml.CreateElement( "VSSWriterServiceAccount" );
                                    $VSSWriterServiceAccountElement.SetAttribute( "type", "DomainUser" ) | Out-Null;
                                    $serviceAccountLoginElement = $xml.CreateElement( "ServiceAccountLogin" );
                                        $serviceAccountLoginElement.InnerText = $VSSWriterServiceAccount.UserName;
                                    $VSSWriterServiceAccountElement.AppendChild( $serviceAccountLoginElement ) | Out-Null;
                                    $serviceAccountPasswordElement = $xml.CreateElement( "ServiceAccountPassword" );
                                        $serviceAccountPasswordElement.InnerText = $VSSWriterServiceAccount.GetNetworkCredential().Password;
                                    $VSSWriterServiceAccountElement.AppendChild( $serviceAccountPasswordElement ) | Out-Null;
                                $serverElement.AppendChild( $VSSWriterServiceAccountElement ) | Out-Null;
                            }
                            if ( $asyncServiceAccount ) {
                                $asyncServiceAccountElement = $xml.CreateElement( "AsyncServiceAccount" );
                                    $asyncServiceAccountElement.SetAttribute( "type", "DomainUser" ) | Out-Null;
                                    $serviceAccountLoginElement = $xml.CreateElement( "ServiceAccountLogin" );
                                        $serviceAccountLoginElement.InnerText = $asyncServiceAccount.UserName;
                                    $asyncServiceAccountElement.AppendChild( $serviceAccountLoginElement ) | Out-Null;
                                    $serviceAccountPasswordElement = $xml.CreateElement( "ServiceAccountPassword" );
                                        $serviceAccountPasswordElement.InnerText = $asyncServiceAccount.GetNetworkCredential().Password;
                                    $asyncServiceAccountElement.AppendChild( $serviceAccountPasswordElement ) | Out-Null;
                                $serverElement.AppendChild( $asyncServiceAccountElement ) | Out-Null;
                            }
                            if ( $monitoringServiceAccount ) {
                                $monitoringServiceAccountElement = $xml.CreateElement( "MonitoringServiceAccount" );
                                    $monitoringServiceAccountElement.SetAttribute( "type", "DomainUser" ) | Out-Null;
                                    $serviceAccountLoginElement = $xml.CreateElement( "ServiceAccountLogin" );
                                        $serviceAccountLoginElement.InnerText = $monitoringServiceAccount.UserName;
                                    $monitoringServiceAccountElement.AppendChild( $serviceAccountLoginElement ) | Out-Null;
                                    $serviceAccountPasswordElement = $xml.CreateElement( "ServiceAccountPassword" );
                                        $serviceAccountPasswordElement.InnerText = $monitoringServiceAccount.GetNetworkCredential().Password;
                                    $monitoringServiceAccountElement.AppendChild( $serviceAccountPasswordElement ) | Out-Null;
                                $serverElement.AppendChild( $monitoringServiceAccountElement ) | Out-Null;
                            }
                            #WebsiteUrl is required
                            $webSiteUrlElement = $xml.CreateElement( "WebsiteUrl" );
                                $webSiteUrlElement.SetAttribute( "create", $createWebSite ) | Out-Null;
                                $webSiteUrlElement.SetAttribute( "port", $webSitePort ) | Out-Null;
                                $webSiteUrlElement.InnerText = $webSiteUrl;
                            $serverElement.AppendChild( $webSiteUrlElement ) | Out-Null;
                            if ( $ServerRoles ) {
                                $rolesElement = $xml.CreateElement( "Roles" );
                                    $ServerRoles | ForEach-Object {
                                        $roleElement = $xml.CreateElement( "Role" );
                                            $roleElement.SetAttribute( "Name", $_ ) | Out-Null;
                                            $roleElement.SetAttribute( "Action", "Add" ) | Out-Null;
                                        $rolesElement.AppendChild( $roleElement ) | Out-Null;
                                    }
                                $serverElement.AppendChild( $rolesElement ) | Out-Null;
                            }
                            if ( $incomingExchangeServer )
                            {
                                $emailElement = $xml.CreateElement( "Email" );
                                    $incomingExchangeServerElement = $xml.CreateElement( "IncomingExchangeServer" );
                                        $incomingExchangeServerElement.SetAttribute( "name", $incomingExchangeServer ) | Out-Null;
                                    $emailElement.AppendChild( $incomingExchangeServerElement ) | Out-Null;
                                $serverElement.AppendChild( $emailElement ) | Out-Null;
                            }
                            if ( $organizationUniqueName ) {
                                $organizationUniqueNameElement = $xml.CreateElement( "OrganizationUniqueName" );
                                    $organizationUniqueNameElement.InnerText = $organizationUniqueName;
                                $serverElement.AppendChild( $organizationUniqueNameElement ) | Out-Null;
                            }
                            if ( $organization ) {
                                $organizationElement = $xml.CreateElement( "Organization" );
                                    $organizationElement.InnerText = $organization;
                                $serverElement.AppendChild( $organizationElement ) | Out-Null;
                            }
                            if ( $baseISOCurrencyCode -or $baseCurrencyName -or $baseCurrencySymbol -or $baseCurrencyPrecision )
                            {
                                $baseCurrencyElement = $xml.CreateElement( "basecurrency" );
                                if ( $baseISOCurrencyCode )
                                {
                                    $baseCurrencyElement.SetAttribute( "isocurrencycode", $baseISOCurrencyCode ) | Out-Null;
                                }
                                if ( $baseCurrencyName )
                                {
                                    $baseCurrencyElement.SetAttribute( "currencyname", $baseCurrencyName ) | Out-Null;
                                }
                                if ( $baseCurrencySymbol )
                                {
                                    $baseCurrencyElement.SetAttribute( "currencysymbol", $baseCurrencySymbol ) | Out-Null;
                                }
                                if ( $baseCurrencyPrecision )
                                {
                                    $baseCurrencyElement.SetAttribute( "currencyprecision", $baseCurrencyPrecision ) | Out-Null;
                                }
                                $serverElement.AppendChild( $baseCurrencyElement ) | Out-Null;
                            }
                            if ( $organizationCollation )
                            {
                                $organizationCollationElement = $xml.CreateElement( "OrganizationCollation" );
                                    $organizationCollationElement.InnerText = $organizationCollation;
                                $serverElement.AppendChild( $organizationCollationElement ) | Out-Null;
                            }
                            if ( $ReportingUrl ) {
                                $reportingElement = $xml.CreateElement( "Reporting" );
                                    $reportingElement.SetAttribute( "URL", $ReportingUrl ) | Out-Null;
                                $serverElement.AppendChild( $reportingElement ) | Out-Null;
                            }
                            $SQMElement = $xml.CreateElement( "SQM" );
                                $SQMElement.SetAttribute( "optin", $SQM ) | Out-Null;
                            $serverElement.AppendChild( $SQMElement ) | Out-Null;
                            $MUOptinElement = $xml.CreateElement( "muoptin" );
                                $MUOptinElement.SetAttribute( "optin", $MUOptin ) | Out-Null;
                            $serverElement.AppendChild( $MUOptinElement ) | Out-Null;
                            $remoteInstallElement = $xml.CreateElement( "remoteinstall" );
                                $remoteInstallElement.InnerText = $true;
                            $serverElement.AppendChild( $remoteInstallElement ) | Out-Null;
                            $rebootElement = $xml.CreateElement( "Reboot" );
                                $rebootElement.InnerText = $reboot;
                            $serverElement.AppendChild( $rebootElement ) | Out-Null;
                        $crmSetupElement.AppendChild( $serverElement ) | Out-Null;
                    $xml.AppendChild( $crmSetupElement ) | Out-Null;

                    $stringWriter = New-Object System.IO.StringWriter;
                    $xmlWriter = New-Object System.Xml.XmlTextWriter $stringWriter;
                    $xmlWriter.Formatting = "indented";
                    $xml.WriteTo( $xmlWriter );
                    $xmlWriter.Flush();
                    $stringWriter.Flush();
                    $xmlConfig = $stringWriter.ToString();

                    if([String]::IsNullOrEmpty($logFilePath) -eq $True) {
                        $timeStamp = ( Get-Date -Format u ).Replace(" ","-").Replace(":","-");
                        $logFilePath = "$env:APPDATA\Microsoft\MSCRM\Logs\CRMInstallationLog_$timeStamp.txt";
                    }
                    $tempFileName = [guid]::NewGuid().Guid;
                    $tempFilePath = "$env:Temp\$tempFileName.xml";

                    Write-Output "$(Get-Date) Saving configuration temporary to $tempFilePath";
                    Set-Content -Path $tempFilePath -Value $xmlConfig -Encoding utf8;
                    Get-Content $tempFilePath | Write-Debug;

                    Write-Output "$(Get-Date) Starting $setupFilePath";

                    $installCrmScript = {
                        param( $setupFilePath, $tempFilePath, $logFilePath );

                        Write-Output "Start-Process '$setupFilePath' -ArgumentList '/Q /config $tempFilePath /L $logFilePath' -Wait;";
                        Start-Process "$setupFilePath" -ArgumentList "/Q /config $tempFilePath /L $logFilePath" -Wait;
                    }

                    $job = Start-Job -ScriptBlock $installCrmScript -ArgumentList $setupFilePath, $tempFilePath, $logFilePath;
                    Write-Output "$(Get-Date) Started installation job, log will be saved in $logFilePath";
                    $lastLinesCount = 0;
                    $startTime = Get-Date;
                    do {
                        $elapsedTime = $( Get-Date ) - $startTime;
                        $elapsedString = "{0:HH:mm:ss}" -f ( [datetime]$elapsedTime.Ticks );
                        Write-Output "$(Get-Date) Elapsed $elapsedString. Waiting until CRM installation job is done, sleeping $logFilePullIntervalInSeconds sec";
                        Start-Sleep $logFilePullIntervalInSeconds;
                        $jobState = $job.State;
                        if ( $logFilePullToOutput ) {
                            if ( Test-Path $logFilePath ) {
                                $logFileContents = Get-Content $logFilePath -ReadCount 0;
                                $linesCount = $logFileContents.Length;
                                $newLinesCount = $linesCount - $lastLinesCount;
                                if($newLinesCount -gt 0) {
                                    Write-Output "$(Get-Date) - new logs: $newLinesCount lines";
                                    $logFileContents | Select-Object -First $newLinesCount -Skip $lastLinesCount | Write-Output;
                                } else {
                                    Write-Output "$(Get-Date) - no new logs";
                                }
                                $lastLinesCount = $linesCount;
                            }
                        }
                    } until ( $jobState -eq "Completed" )
                    $elapsedTime = $( Get-Date ) - $startTime;
                    $elapsedString = "{0:HH:mm:ss}" -f ( [datetime]$elapsedTime.Ticks );
                    Write-Output "$(Get-Date) Elapsed $elapsedString. Job is complete, output:";
                    Write-Output ( Receive-Job $job );

                    Remove-Job $job;
                    Start-Sleep 10;
                    Remove-Item $tempFilePath;
                    if( (Test-Path $logFilePath) -eq $True) {
                        $errorLines = Get-Content $logFilePath | Select-String -Pattern "Error|" -SimpleMatch;
                        if ( $errorLines ) {
                            Write-Output "Errors from install log:";
                            $errorLines | Foreach-Object {
                                Write-Output $_.Line;
                            }
                        }
                    }
                    $msCRMRegistryValues = Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MSCRM -ErrorAction Ignore;
                    if ( $msCRMRegistryValues ) {
                        $isInstalled = $true;
                        $installedLanguage = Get-Dynamics365ServerLanguage;
                        if ( $installedLanguage -ne $fileLanguageCode ) {
                            $errorMessage = "Another language is installed: $installedLanguage";
                            Write-Output $errorMessage;
                            Throw $errorMessage;
                        }
                    } else {
                        $isInstalled = $false;
                    }
                    if ( $isInstalled ) {
                        $installedRoles = Get-Dynamics365ServerRole;
                        Write-Output "Found Roles: ";
                        $installedRoles | Write-Output;
                        if ( $ServerRoles ) {
                            $ServerRoles | ForEach-Object {
                                if ( !( $installedRoles -contains $_ -or ( $_ -eq "VssWriter" -and $installedRoles -contains "VssWriterService" ) ) ) {
                                    $errorMessage = "Installation job finished but $_ role is not installed";
                                    Write-Output $errorMessage;
                                    Throw $errorMessage;
                                }
                            }
                        }
                        Write-Output "Installation is finished and verified successfully";
                    } else {
                        $errorMessage = "Installation job finished but the product is still not installed";
                        Write-Output $errorMessage;
                        Throw $errorMessage;
                    }
                } else {
                    Write-Output "This product is already installed, skipping";
                }
            } else {
                $errorMessage = "Single language is not found in \LangPacks directory";
                Write-Output $errorMessage;
                Throw $errorMessage;
            }
        } else {
            $errorMessage = "\LangPacks directory is not found";
            Write-Output $errorMessage;
            Throw $errorMessage;
        }
    } else {
        $errorMessage = "\SetupServer.exe file is not found";
        Write-Output $errorMessage;
        Throw $errorMessage;
    }
}