AutomatedLabDynamics.psm1
function Install-LabDynamics { [CmdletBinding()] param ( [switch] $CreateCheckPoints ) Write-LogFunctionEntry $lab = Get-Lab -ErrorAction Stop $vms = Get-LabVm -Role Dynamics $sql = Get-LabVm -Role SQLServer2016, SQLServer2017 | Sort-Object { $_.Roles.Name } | Select-Object -Last 1 Start-LabVM -ComputerName $vms -Wait Invoke-LabCommand -ComputerName $vms -ScriptBlock { if (-not (Test-Path C:\DeployDebug)) { $null = New-Item -ItemType Directory -Path C:\DeployDebug } if (-not (Test-Path C:\DynamicsSetup)) { $null = New-Item -ItemType Directory -Path C:\DynamicsSetup } } -NoDisplay $someDc = Get-LabVm -Role RootDc | Select -First 1 $defaultDomain = Invoke-LabCommand -ComputerName $someDc -ScriptBlock { Get-ADDomain } -PassThru -NoDisplay # Download prerequisites (which are surprisingly old...) Write-ScreenInfo -Message "Downloading and installing prerequisites on $($vms.Count) machines" $downloadTargetFolder = "$labSources\SoftwarePackages" $dynamicsUri = Get-LabConfigurationItem -Name Dynamics365Uri $cppRedist64_2013 = Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name cppredist64_2013) -Path $downloadTargetFolder -FileName vcredist_x64_2013.exe -PassThru -NoDisplay $cppRedist64_2010 = Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name cppredist64_2010) -Path $downloadTargetFolder -FileName vcredist_x64_2010.exe -PassThru -NoDisplay $odbc = Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name SqlOdbc13) -Path $downloadTargetFolder -FileName odbc2013.msi -PassThru -NoDisplay $sqlServerNativeClient2012 = Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name SqlServerNativeClient2012) -Path $downloadTargetFolder -FileName sqlncli2012.msi -PassThru -NoDisplay $sqlClrType = Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name SqlClrType2016) -Path $downloadTargetFolder -FileName sqlclrtype2016.msi -PassThru -NoDisplay $sqlSmo = Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name SqlSmo2016) -Path $downloadTargetFolder -FileName sqlsmo2016.msi -PassThru -NoDisplay $installer = Get-LabInternetFile -Uri $dynamicsUri -Path $labSources/SoftwarePackages -PassThru -NoDisplay Install-LabSoftwarePackage -ComputerName $vms -Path $installer.FullName -CommandLine '/extract:C:\DynamicsSetup /quiet' -NoDisplay Install-LabSoftwarePackage -Path $cppRedist64_2010.FullName -Computer $vms -CommandLine '/quiet' -NoDisplay Install-LabSoftwarePackage -Path $cppRedist64_2013.FullName -Computer $vms -CommandLine '/s' -NoDisplay Install-LabSoftwarePackage -Path $odbc.FullName -ComputerName $vms -CommandLine '/QN ADDLOCAL=ALL IACCEPTMSODBCSQLLICENSETERMS=YES /L*v C:\odbc.log' -NoDisplay Install-LabSoftwarePackage -Path $sqlServerNativeClient2012.FullName -ComputerName $vms -CommandLine '/QN IACCEPTSQLNCLILICENSETERMS=YES' -NoDisplay Install-LabSoftwarePackage -Path $sqlClrType.FullName -ComputerName $vms -NoDisplay Install-LabSoftwarePackage -Path $sqlSmo.FullName -ComputerName $vms -NoDisplay [xml]$defaultXml = @" <CRMSetup> <Server> <Patch update="false" /> <LicenseKey>KKNV2-4YYK8-D8HWD-GDRMW-29YTW</LicenseKey> <SqlServer>$sql</SqlServer> <Database create="true"/> <Reporting URL="http://$sql/ReportServer"/> <OrganizationCollation>Latin1_General_CI_AI</OrganizationCollation> <basecurrency isocurrencycode="USD" currencyname="US Dollar" currencysymbol="$" currencyprecision="2"/> <Organization>AutomatedLab</Organization> <OrganizationUniqueName>automatedlab</OrganizationUniqueName> <WebsiteUrl create="true" port="5555"> </WebsiteUrl> <InstallDir>c:\Program Files\Microsoft Dynamics CRM</InstallDir> <CrmServiceAccount type="DomainUser"> <ServiceAccountLogin>$($defaultDomain.Name)\CRMAppService</ServiceAccountLogin> <ServiceAccountPassword>$($lab.DefaultInstallationCredential.Password)</ServiceAccountPassword> </CrmServiceAccount> <SandboxServiceAccount type="DomainUser"> <ServiceAccountLogin>$($defaultDomain.Name)\CRMSandboxService</ServiceAccountLogin> <ServiceAccountPassword>$($lab.DefaultInstallationCredential.Password)</ServiceAccountPassword> </SandboxServiceAccount> <DeploymentServiceAccount type="DomainUser"> <ServiceAccountLogin>$($defaultDomain.Name)\CRMDeploymentService</ServiceAccountLogin> <ServiceAccountPassword>$($lab.DefaultInstallationCredential.Password)</ServiceAccountPassword> </DeploymentServiceAccount> <AsyncServiceAccount type="DomainUser"> <ServiceAccountLogin>$($defaultDomain.Name)\CRMAsyncService</ServiceAccountLogin> <ServiceAccountPassword>$($lab.DefaultInstallationCredential.Password)</ServiceAccountPassword> </AsyncServiceAccount> <VSSWriterServiceAccount type="DomainUser"> <ServiceAccountLogin>$($defaultDomain.Name)\CRMVSSWriterService</ServiceAccountLogin> <ServiceAccountPassword>$($lab.DefaultInstallationCredential.Password)</ServiceAccountPassword> </VSSWriterServiceAccount> <MonitoringServiceAccount type="DomainUser"> <ServiceAccountLogin>$($defaultDomain.Name)\CRMMonitoringService</ServiceAccountLogin> <ServiceAccountPassword>$($lab.DefaultInstallationCredential.Password)</ServiceAccountPassword> </MonitoringServiceAccount> <SQM optin="false"/> <muoptin optin="false"/> <Groups AutoGroupManagementOff="false"> <PrivUserGroup>CN=PrivUserGroup,OU=CRM,$($defaultDomain.DistinguishedName)</PrivUserGroup> <SQLAccessGroup>CN=SQLAccessGroup,OU=CRM,$($defaultDomain.DistinguishedName)</SQLAccessGroup> <ReportingGroup>CN=ReportingGroup,OU=CRM,$($defaultDomain.DistinguishedName)</ReportingGroup> <PrivReportingGroup>CN=PrivReportingGroup,OU=CRM,$($defaultDomain.DistinguishedName)</PrivReportingGroup> </Groups> </Server> </CRMSetup> "@ [xml]$frontendRole = @" <RoleConfig> <Roles> <Role Name="WebApplicationServer" /> <Role Name="OrganizationWebService" /> <Role Name="DiscoveryWebService" /> <Role Name="HelpServer" /> </Roles> </RoleConfig> "@ [xml]$backendRole = @" <RoleConfig> <Roles> <Role Name="AsynchronousProcessingService" /> <Role Name="EmailConnector" /> <Role Name="SandboxProcessingService" /> </Roles> </RoleConfig> "@ [xml]$adminRole = @" <RoleConfig> <Roles> <Role Name="DeploymentTools" /> <Role Name="DeploymentWebService" /> <Role Name="VSSWriter" /> </Roles> </RoleConfig> "@ Write-ScreenInfo -Message "Installing Dynamics 365 CRM on $($vms.Count) machines" $orgFirstDeployed = @{ } foreach ($vm in $vms) { $role = $vm.Roles | Where-Object { $_.Name -band [AutomatedLab.Roles]::Dynamics } $serverXml = $defaultXml.Clone() foreach ($property in $role.Properties.Keys) { switch ($property.Key) { 'SqlServer' { $sql = Get-LabVm -ComputerName $property.Value $serverXml.CRMSetup.Server.SqlServer = $property.Value } 'ReportingUrl' { $serverXml.CRMSetup.Server.Reporting.URL = $property.Value } 'OrganizationCollation' { $serverXml.CRMSetup.Server.OrganizationCollation = $property.Value } 'IsoCurrencyCode' { $serverXml.CRMSetup.Server.basecurrency.isocurrencycode = $property.Value } 'CurrencyName' { $serverXml.CRMSetup.Server.currencyname.isocurrencycode = $property.Value } 'CurrencySymbol' { $serverXml.CRMSetup.Server.basecurrency.currencysymbol = $property.Value } 'CurrencyPrecision' { $serverXml.CRMSetup.Server.basecurrency.currencyprecision = $property.Value } 'Organization' { $serverXml.CRMSetup.Server.Organization = $property.Value } 'OrganizationUniqueName' { $serverXml.CRMSetup.Server.OrganizationUniqueName = $property.Value } 'CrmServiceAccount' { $serverXml.CRMSetup.Server.CrmServiceAccount.ServiceAccountLogin = $property.Value } 'SandboxServiceAccount' { $serverXml.CRMSetup.Server.SandboxServiceAccount.ServiceAccountLogin = $property.Value } 'DeploymentServiceAccount' { $serverXml.CRMSetup.Server.DeploymentServiceAccount.ServiceAccountLogin = $property.Value } 'AsyncServiceAccount' { $serverXml.CRMSetup.Server.AsyncServiceAccount.ServiceAccountLogin = $property.Value } 'VSSWriterServiceAccount' { $serverXml.CRMSetup.Server.VSSWriterServiceAccount.ServiceAccountLogin = $property.Value } 'MonitoringServiceAccount' { $serverXml.CRMSetup.Server.MonitoringServiceAccount.ServiceAccountLogin = $property.Value } 'CrmServiceAccountPassword' { $serverXml.CRMSetup.Server.CrmServiceAccount.ServiceAccountPassword = $property.Value } 'SandboxServiceAccountPassword' { $serverXml.CRMSetup.Server.SandboxServiceAccount.ServiceAccountPassword = $property.Value } 'DeploymentServiceAccountPassword' { $serverXml.CRMSetup.Server.DeploymentServiceAccount.ServiceAccountPassword = $property.Value } 'AsyncServiceAccountPassword' { $serverXml.CRMSetup.Server.AsyncServiceAccount.ServiceAccountPassword = $property.Value } 'VSSWriterServiceAccountPassword' { $serverXml.CRMSetup.Server.VSSWriterServiceAccount.ServiceAccountPassword = $property.Value } 'MonitoringServiceAccountPassword' { $serverXml.CRMSetup.Server.MonitoringServiceAccount.ServiceAccountPassword = $property.Value } 'IncomingExchangeServer' { $node = $serverXml.CreateElement('Email') $incoming = $serverXml.CreateElement('IncomingExchangeServer') $attr = $serverXml.CreateAttribute('name') $attr.InnerText = $property.Value $null = $incoming.Attributes.Append($attr) $null = $node.AppendChild($incoming) $null = $serverXml.CRMSetup.Server.AppendChild($node) } 'PrivUserGroup' { $serverXml.CRMSetup.Server.Groups.PrivUserGroup = $property.Value } 'SQLAccessGroup' { $serverXml.CRMSetup.Server.Groups.SQLAccessGroup = $property.Value } 'ReportingGroup' { $serverXml.CRMSetup.Server.Groups.ReportingGroup = $property.Value } 'PrivReportingGroup' { $serverXml.CRMSetup.Server.Groups.PrivReportingGroup = $property.Value } 'LicenseKey' { $node = $serverXml.CreateElement('LicenseKey') $node.InnerText = $property.Value $null = $serverXml.CRMSetup.Server.AppendChild($node) } } } if ($orgFirstDeployed.Contains($serverXml.CRMSetup.Server.OrganizationUniqueName)) { $serverXml.CRMSetup.Server.Database.create = 'False' } if (-not $orgFirstDeployed.Contains($serverXml.CRMSetup.Server.OrganizationUniqueName)) { $orgFirstDeployed[$serverXml.CRMSetup.Server.OrganizationUniqueName] = $vm.Name } if ($role.Name -eq [AutomatedLab.Roles]::DynamicsFrontend) { $lab.AzureSettings.LoadBalancerPortCounter++ $remotePort = $lab.AzureSettings.LoadBalancerPortCounter Write-ScreenInfo -Message ('Connection to dynamics frontend via http://{0}:{1}' -f $vm.AzureConnectionInfo.DnsName, $remotePort) Add-LWAzureLoadBalancedPort -ComputerName $vm -DestinationPort $serverXml.CRMSetup.Server.WebsiteUrl.port -Port $remotePort $node = $serverXml.ImportNode($frontendRole.RoleConfig, $true) $null = $serverXml.CRMSetup.Server.AppendChild($node.Roles) } if ($role.Name -eq [AutomatedLab.Roles]::DynamicsBackend) { $node = $serverXml.ImportNode($backendRole.RoleConfig, $true) $null = $serverXml.CRMSetup.Server.AppendChild($node.Roles) } if ($role.Name -eq [AutomatedLab.Roles]::DynamicsAdmin) { $node = $serverXml.ImportNode($adminRole.RoleConfig, $true) $null = $serverXml.CRMSetup.Server.AppendChild($node.Roles) } # Begin AD Prep [hashtable[]] $users = foreach ($node in $serverXml.SelectNodes('/CRMSetup/Server/*[contains(name(), "Account")]')) { @{ Name = $node.ServiceAccountLogin -replace '.*\\' AccountPassword = ConvertTo-SecureString -String $node.ServiceAccountPassword -AsPlainText -Force Enabled = $true ErrorAction = 'Stop' } } [hashtable[]] $groups = foreach ($node in $serverXml.SelectNodes('/CRMSetup/Server/Groups/*[contains(name(), "Group")]')) { $null = $node.InnerText -match 'CN=(\w+),OU' @{ Name = $Matches.1 GroupScope = 'DomainLocal' GroupCategory = 'Security' Path = $node.InnerText -replace 'CN=\w+,' ErrorAction = 'Stop' } } $ous = $groups.Path | Sort-Object -Unique $sqlRole = $sql.Roles | Where-Object { $_.Name -band [AutomatedLab.Roles]::SQLServer } $memberships = @{ $serverXml.CRMSetup.Server.Groups.PrivUserGroup = @( $serverXml.CRMSetup.Server.CrmServiceAccount.ServiceAccountLogin $serverXml.CRMSetup.Server.DeploymentServiceAccount.ServiceAccountLogin $serverXml.CRMSetup.Server.AsyncServiceAccount.ServiceAccountLogin $serverXml.CRMSetup.Server.VSSWriterServiceAccount.ServiceAccountLogin ($sqlRole.Properties.GetEnumerator | Where-Object Key -like *Account).Value ) $serverXml.CRMSetup.Server.Groups.SQLAccessGroup = @( $serverXml.CRMSetup.Server.CrmServiceAccount.ServiceAccountLogin $serverXml.CRMSetup.Server.DeploymentServiceAccount.ServiceAccountLogin $serverXml.CRMSetup.Server.AsyncServiceAccount.ServiceAccountLogin $serverXml.CRMSetup.Server.VSSWriterServiceAccount.ServiceAccountLogin ($sqlRole.Properties.GetEnumerator | Where-Object Key -like *Account).Value ) $serverXml.CRMSetup.Server.Groups.ReportingGroup = @( $lab.DefaultInstallationCredential.UserName ) $serverXml.CRMSetup.Server.Groups.PrivReportingGroup = @( ($sqlRole.Properties.GetEnumerator | Where-Object Key -like *Account).Value ) } Invoke-LabCommand -ActivityName 'Enabling SQL Server Agent' -ComputerName $sql -ScriptBlock { Get-Service -Name *SQLSERVERAGENT* | Set-Service -StartupType Automatic -Status Running } -NoDisplay Invoke-LabCommand -ActivityName 'Preparing accounts, groups and OUs' -ComputerName $someDc -ScriptBlock { foreach ($ou in $ous) { $null = $ou -match '^OU=(?<Name>\w+),' $ouName = $Matches.Name $path = $ou -replace '^OU=(\w+),' try { New-ADOrganizationalUnit -Name $ouName -Path $path -ErrorAction Stop } catch {} } foreach ($user in $users) { try { New-ADUser @user } catch {} } foreach ($group in $groups) { try { New-ADGroup @group } catch {} } foreach ($membership in $memberships.GetEnumerator()) { if (-not $membership.Value) { continue } Add-ADGroupMember -Identity $membership.Key -Members ($membership.Value -replace '.*\\' | Where-Object { $_ }) } } -Variable (Get-Variable groups, ous, users, memberships) -NoDisplay Invoke-LabCommand -ComputerName $vm -ScriptBlock { # Using SID instead of name 'Performance Log Users' to avoid possible translation issues Add-LocalGroupMember -SID 'S-1-5-32-559' -Member $serverxml.crmsetup.Server.AsyncServiceAccount.ServiceAccountLogin, $serverxml.crmsetup.Server.CrmServiceAccount.ServiceAccountLogin $serverXml.Save('C:\DeployDebug\Dynamics.xml') } -Variable (Get-Variable serverXml) -NoDisplay } Restart-LabVM -ComputerName $vms -Wait -NoDisplay $timeout = if ($lab.DefaultVirtualizationEngine -eq 'Azure') { 60 } else { 45 } Install-LabSoftwarePackage -ComputerName $orgFirstDeployed.Values -LocalPath 'C:\DynamicsSetup\SetupServer.exe' -CommandLine '/config C:\DeployDebug\Dynamics.xml /log C:\DeployDebug\DynamicsSetup.log /Q' -ExpectedReturnCodes 0, 3010 -NoDisplay -UseShellExecute -AsScheduledJob -UseExplicitCredentialsForScheduledJob -Timeout $timeout $remainingVms = $vms | Where-Object -Property Name -notin $orgFirstDeployed.Values if ($remainingVms) { Install-LabSoftwarePackage -ComputerName $remainingVms -LocalPath 'C:\DynamicsSetup\SetupServer.exe' -CommandLine '/config C:\DeployDebug\Dynamics.xml /log C:\DeployDebug\DynamicsSetup.log /Q' -ExpectedReturnCodes 0, 3010 -NoDisplay -UseShellExecute -AsScheduledJob -UseExplicitCredentialsForScheduledJob -Timeout $timeout } if ($CreateCheckPoints.IsPresent) { Checkpoint-LabVM -ComputerName $vms -SnapshotName AfterDynamicsInstall } Write-LogFunctionExit } |