diff --git a/CHANGELOG.md b/CHANGELOG.md index 73ad4b37c..a7f3defa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SharePointDsc - Added generic unit tests files to quickly run all or a specific unit test - Updated pipeline scripts to a recent version + - Added an extensive flexible configuration to deploy a SharePoint environment +- SPDistributedCacheService + - Added documentation to clarify the use of the ServerProvisionOrder parameter - SPTrustedIdentityTokenIssuer - Added parameters to support OIDC authentication in SharePoint Server Subscription Edition - SPWebAppPeoplePickerSettings - Added the PeopleEditorOnlyResolveWithinSiteCollection parameter to the resource -- SPDistributedCacheService - - Added documentation to clarify the use of the ServerProvisionOrder parameter ### Changed @@ -28,6 +29,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- SPSearchIndexPartition + - Fixed issue where the Get method returned multiple values when using multiple + index components + - Fixed issue the Export would place quotes around a variable in the output +- SPSearchTopology + - Fixed issue the Export would place quotes around a variable in the output - SPTrustedRootAuthority - Fixed issue where certificates not in the Personal store could not be used - Add-SPDscConfigDBLock diff --git a/SharePointDsc/DSCResources/MSFT_SPSearchIndexPartition/MSFT_SPSearchIndexPartition.psm1 b/SharePointDsc/DSCResources/MSFT_SPSearchIndexPartition/MSFT_SPSearchIndexPartition.psm1 index e199bc338..1315de27c 100644 --- a/SharePointDsc/DSCResources/MSFT_SPSearchIndexPartition/MSFT_SPSearchIndexPartition.psm1 +++ b/SharePointDsc/DSCResources/MSFT_SPSearchIndexPartition/MSFT_SPSearchIndexPartition.psm1 @@ -35,7 +35,7 @@ function Get-TargetResource Where-Object -FilterScript { ($_.GetType().Name -eq "IndexComponent") ` -and ($_.IndexPartitionOrdinal -eq $params.Index) - } + } | Select-Object -First 1 $IndexComponents = $searchComponent.ServerName $rootDirectory = $searchComponent.RootDirectory @@ -286,7 +286,7 @@ function Export-TargetResource $ssa = Get-SPEnterpriseSearchServiceApplication -Identity $ssa $currentTopology = $ssa.ActiveTopology $indexComponents = Get-SPEnterpriseSearchComponent -SearchTopology $currentTopology | ` - Where-Object -FilterScript { + Where-Object -FilterScript { $_.GetType().Name -eq "IndexComponent" } @@ -318,6 +318,7 @@ function Export-TargetResource $currentBlock = Get-DSCBlock -Params $results -ModulePath $module $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "PsDscRunAsCredential" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "Servers" $PartialContent += $currentBlock $PartialContent += " }`r`n" $Content += $PartialContent diff --git a/SharePointDsc/DSCResources/MSFT_SPSearchTopology/MSFT_SPSearchTopology.psm1 b/SharePointDsc/DSCResources/MSFT_SPSearchTopology/MSFT_SPSearchTopology.psm1 index 65a8c09be..6376000a0 100644 --- a/SharePointDsc/DSCResources/MSFT_SPSearchTopology/MSFT_SPSearchTopology.psm1 +++ b/SharePointDsc/DSCResources/MSFT_SPSearchTopology/MSFT_SPSearchTopology.psm1 @@ -638,6 +638,12 @@ function Export-TargetResource $currentBlock = Get-DSCBlock -Params $results -ModulePath $module $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "PsDscRunAsCredential" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "Admin" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "AnalyticsProcessing" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "ContentProcessing" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "Crawler" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "IndexPartition" + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "QueryProcessing" $PartialContent += $currentBlock $PartialContent += " }`r`n" $Content += $PartialContent diff --git a/SharePointDsc/FarmDeployment/Deploy_SharePoint.ps1 b/SharePointDsc/FarmDeployment/Deploy_SharePoint.ps1 new file mode 100644 index 000000000..a37002594 --- /dev/null +++ b/SharePointDsc/FarmDeployment/Deploy_SharePoint.ps1 @@ -0,0 +1,1598 @@ +### NOTE: This script hasn't been fully tested yet. We are in the process of doing that. +### But wanted to share it anyways, so it can be used as an example by others. + +##### GENERIC VARIABLES ##### +$buildingBlockVersion = [System.Version]'1.0.0' + +##### DSC CONFIGURATION ##### +Configuration Deploy_SP +{ + param + ( + [Parameter(Mandatory = $true)] [ValidateNotNullorEmpty()] [PSCredential[]] $Credentials, + [Parameter(Mandatory = $true)] [ValidateNotNullorEmpty()] [PSCredential] $InstallAccount, + [Parameter(Mandatory = $true)] [ValidateNotNullorEmpty()] [PSCredential] $PassPhrase, + [Parameter(Mandatory = $true)] [ValidateNotNullorEmpty()] [PSCredential] $CertificatePassword + ) + + Import-DscResource -ModuleName PSDesiredStateConfiguration + Import-DscResource -ModuleName SharePointDsc + Import-DscResource -ModuleName CertificateDsc + Import-DscResource -ModuleName xWebAdministration + Import-DscResource -ModuleName xCredSSP + Import-DscResource -ModuleName ComputerManagementDsc + + # Determine SharePoint Frontend and Backend servers based on Subrole property + $spServers = ($AllNodes | Where-Object { $_.Role -eq "SharePoint" }).NodeName + $feServers = ($AllNodes | Where-Object { $_.Role -eq "SharePoint" -and $_.Subrole -contains "SPFE" } ).NodeName + $beServers = ($AllNodes | Where-Object { $_.Role -eq "SharePoint" -and $_.Subrole -contains "SPBE" }).NodeName + $firstBEServer = $beServers | Select-Object -First 1 + + # Determine SharePoint Search servers based on Subrole property + $searchServers = ($AllNodes | Where-Object { $_.Role -eq "SharePoint" -and ($_.Subrole -contains "SearchBE" -or $_.Subrole -contains "SearchFE") } ).NodeName + $searchFEServers = ($AllNodes | Where-Object { $_.Role -eq "SharePoint" -and $_.Subrole -contains "SearchFE" } ).NodeName + $searchBEServers = ($AllNodes | Where-Object { $_.Role -eq "SharePoint" -and $_.Subrole -contains "SearchBE" } ).NodeName + $firstSearchBEServer = $searchBEServers | Select-Object -First 1 + + # Define install folders + $installFolder = $ConfigurationData.NonNodeData.InstallPaths.InstallFolder + $installSPFolder = Join-Path -Path $installFolder -ChildPath "SharePoint" + $installSPBinFolder = Join-Path -Path $installSPFolder -ChildPath "Install" + $installSPPrereqFolder = Join-Path -Path $installSPBinFolder -ChildPath "prerequisiteinstallerfiles" + $installSPLPFolder = Join-Path -Path $installSPFolder -ChildPath "LanguagePackNL" + $installSPCUFolder = Join-Path -Path $installSPFolder -ChildPath "CU" + + node $AllNodes.NodeName + { + #region SharePoint servers + if ($spServers -contains $Node.NodeName) + { + Group 'FarmAccountPerformanceMonitorUsersGroup' + { + GroupName = 'Performance Monitor Users' + MembersToInclude = @($ConfigurationData.NonNodeData.ManagedAccounts.Farm, ` + $ConfigurationData.NonNodeData.ManagedAccounts.AppPool, ` + $ConfigurationData.NonNodeData.ManagedAccounts.Services) + Ensure = 'Present' + } + + # Disable SSL 2.0, SSL 3.0, TLS 1.0 en TLS 1.1: Only TLS 1.2 is allowed. More info: + # - https://docs.microsoft.com/en-us/windows/desktop/secauthn/protocols-in-tls-ssl--schannel-ssp- + # - https://docs.microsoft.com/en-us/sharepoint/security-for-sharepoint-server/enable-tls-1-1-and-tls-1-2-support-in-sharepoint-server-2019 + Registry 'SSL2_Client_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 2.0\Client' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + + Registry 'SSL2_Client_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 2.0\Client' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'SSL3_Client_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 3.0\Client' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'SSL3_Client_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 3.0\Client' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.0_Client_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.0\Client' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'TLS1.0_Client_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.0\Client' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.1_Client_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.1\Client' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'TLS1.1_Client_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.1\Client' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.2_Client_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.2\Client' + ValueName = 'Enabled' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.2_Client_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.2\Client' + ValueName = 'DisabledByDefault' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'SSL2_Server_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 2.0\Server' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'SSL2_Server_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 2.0\Server' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'SSL3_Server_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 3.0\Server' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'SSL3_Server_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\SSL 3.0\Server' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.0_Server_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.0\Server' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'TLS1.0_Server_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.0\Server' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.1_Server_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.1\Server' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'TLS1.1_Server_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.1\Server' + ValueName = 'DisabledByDefault' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.2_Server_ConfigureEnabled' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.2\Server' + ValueName = 'Enabled' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'TLS1.2_Server_ConfigureDisabledByDefault' + { + Ensure = 'Present' + Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\TLS 1.2\Server' + ValueName = 'DisabledByDefault' + ValueData = '0' + ValueType = 'Dword' + } + + # https://docs.microsoft.com/en-us/officeonlineserver/enable-tls-1-1-and-tls-1-2-support-in-office-online-server + Registry 'Enable_Strong_Crypto_NET_64' + { + Ensure = 'Present' + Key = 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' + ValueName = 'SchUseStrongCrypto' + ValueData = '1' + ValueType = 'Dword' + } + + Registry 'Enable_Strong_Crypto_NET_32' + { + Ensure = 'Present' + Key = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319' + ValueName = 'SchUseStrongCrypto' + ValueData = '1' + ValueType = 'Dword' + } + + # Gebaseerd op https://docs.microsoft.com/en-us/windows/desktop/secauthn/tls-cipher-suites-in-windows-10-v1607 + Registry 'ConfigureAllowedCipherSuites' + { + Ensure = 'Present' + Key = 'HKLM:\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002' + ValueName = 'Functions' + ValueData = 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,TLS_PSK_WITH_AES_256_GCM_SHA384,TLS_PSK_WITH_AES_128_GCM_SHA256,TLS_PSK_WITH_AES_256_CBC_SHA384,TLS_PSK_WITH_AES_128_CBC_SHA256' + ValueType = 'String' + } + + # Moet uit staan: https://docs.microsoft.com/en-us/sharepoint/security-for-sharepoint-server/federal-information-processing-standard-security-standards + Registry 'DisableFIPSAlgorithmPolicy' + { + Ensure = 'Present' + Key = 'HKLM:\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy' + ValueName = 'Enabled' + ValueData = '0' + ValueType = 'Dword' + } + + Registry 'DisableLoopBackCheck' + { + Ensure = 'Present' + Key = 'HKLM:\System\CurrentControlSet\Control\Lsa' + ValueName = 'DisableLoopbackCheck' + ValueData = '1' + ValueType = 'Dword' + } + + xCredSSP 'Server' + { + Ensure = 'Present' + Role = 'Server' + } + + xCredSSP 'Client' + { + Ensure = 'Present' + Role = 'Client' + DelegateComputers = @("$($Node.NodeName)", "$($Node.NodeName).$($ConfigurationData.NonNodeData.DomainDetails.DomainName)") + } + + + File 'IISLogFolder' + { + DestinationPath = $ConfigurationData.NonNodeData.Logging.IISLogPath + Type = 'Directory' + Ensure = 'Present' + + } + + File 'UsageLogFolder' + { + DestinationPath = $ConfigurationData.NonNodeData.Logging.UsageLogPath + Type = 'Directory' + Ensure = 'Present' + + } + + if ($feServers -contains $Node.NodeName -or $beServers -contains $Node.NodeName) + { + PfxImport 'SSL_Portal_Contoso_local' + { + Thumbprint = $ConfigurationData.NonNodeData.Certificates.Portal.Thumbprint.ToUpper() + Path = (Join-Path -Path $ConfigurationData.NonNodeData.InstallPaths.CertificatesFolder -ChildPath $ConfigurationData.NonNodeData.Certificates.Portal.File) + Location = 'LocalMachine' + Store = 'My' + Credential = $CertificatePassword + } + } + + if ($feServers -contains $Node.NodeName -and + $ConfigurationData.NonNodeData.SharePoint.ProvisionApps -eq $true) + { + PfxImport 'SSL_Portal_ContosoApps_local' + { + Thumbprint = $ConfigurationData.NonNodeData.Certificates.PortalApps.Thumbprint.ToUpper() + Path = (Join-Path -Path $ConfigurationData.NonNodeData.InstallPaths.CertificatesFolder -ChildPath $ConfigurationData.NonNodeData.Certificates.PortalApps.File) + Location = 'LocalMachine' + Store = 'My' + Credential = $CertificatePassword + } + } + + # Required to resolve a bug in the Prereqs installer, which does accepts an incorrect version + # of the Visual C++ 2017 library: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads + # More info: https://docs.microsoft.com/en-us/sharepoint/troubleshoot/installation-and-setup/sharepoint-server-setup-fails + Package 'Install_VC2017ReDistx64' + { + Name = 'Microsoft Visual C++ 2015-2019 Redistributable (x64) - 14.24.28127' + Path = (Join-Path -Path $installSPPrereqFolder -ChildPath 'vc_redist.x64.exe') + Arguments = '/quiet /norestart' + ProductId = '282975d8-55fe-4991-bbbb-06a72581ce58' + Ensure = 'Present' + Credential = $InstallAccount + } + + SPInstallPrereqs 'Install_SP_Prereqs' + { + IsSingleInstance = 'Yes' + InstallerPath = (Join-Path -Path $installSPBinFolder -ChildPath 'prerequisiteinstaller.exe') + OnlineMode = $false + AppFabric = (Join-Path -Path $installSPPrereqFolder -ChildPath 'WindowsServerAppFabricSetup_x64.exe') + DotNetFX = (Join-Path -Path $installSPPrereqFolder -ChildPath 'dotNetFx45_Full_setup.exe') + DotNet472 = (Join-Path -Path $installSPPrereqFolder -ChildPath 'NDP472-KB4054530-x86-x64-AllOS-ENU.exe') + KB3092423 = (Join-Path -Path $installSPPrereqFolder -ChildPath 'AppFabric-KB3092423-x64-ENU.exe') + IDFX11 = (Join-Path -Path $installSPPrereqFolder -ChildPath 'MicrosoftIdentityExtensions-64.msi') + MSIPCClient = (Join-Path -Path $installSPPrereqFolder -ChildPath 'setup_msipc_x64.exe') + MSVCRT11 = (Join-Path -Path $installSPPrereqFolder -ChildPath 'vcredist_x64.exe') + MSVCRT141 = (Join-Path -Path $installSPPrereqFolder -ChildPath 'vc_redist.x64.exe') + SQLNCli = (Join-Path -Path $installSPPrereqFolder -ChildPath 'sqlncli.msi') + Sync = (Join-Path -Path $installSPPrereqFolder -ChildPath 'Synchronization.msi') + WCFDataServices56 = (Join-Path -Path $installSPPrereqFolder -ChildPath 'WcfDataServices.exe') + Ensure = 'Present' + PSDscRunAsCredential = $InstallAccount + DependsOn = '[Package]Install_VC2017ReDistx64' + } + + SPInstall 'Install_SharePoint' + { + IsSingleInstance = 'Yes' + BinaryDir = $installSPBinFolder + ProductKey = $ConfigurationData.NonNodeData.SharePoint.ProductKey + InstallPath = $ConfigurationData.NonNodeData.SharePoint.InstallPath + DataPath = $ConfigurationData.NonNodeData.SharePoint.DataPath + PSDscRunAsCredential = $InstallAccount + DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + +<# Commented out to prevent LP to be installed. Update if you need to install LP. + SPInstallLanguagePack 'Install_NL_LP_Binaries' + { + BinaryDir = $installSPLPFolder + Ensure = 'Present' + PSDscRunAsCredential = $InstallAccount + DependsOn = '[SPInstall]Install_SharePoint' + } +#> + + SPProductUpdate 'LanguageDependant_CU' + { + SetupFile = (Join-Path -Path $installSPCUFolder -ChildPath $ConfigurationData.NonNodeData.SharePoint.CULangFileName) + ShutdownServices = $false + Ensure = 'Present' + PSDscRunAsCredential = $InstallAccount + DependsOn = '[SPInstall]Install_SharePoint' + } + + SPProductUpdate 'Language_Independant_CU' + { + SetupFile = (Join-Path -Path $installSPCUFolder -ChildPath $ConfigurationData.NonNodeData.SharePoint.CUFileName) + ShutdownServices = $false + Ensure = 'Present' + PSDscRunAsCredential = $InstallAccount + DependsOn = '[SPProductUpdate]LanguageDependant_CU' + } + + # Determine MinRole based on Subrole property + $wfe = $false + $be = $false + $search = $false + switch ($Node.Subrole) + { + 'SPFE' { $wfe = $true } + 'SPBE' { $be = $true } + { $_ -in ('SearchFE', 'SearchBE') } { $search = $true } + } + + if ($wfe -eq $true -and $be -eq $false -and $search -eq $false) + { + $minRole = 'WebFrontEndWithDistributedCache' + } + elseif ($wfe -eq $false -and $be -eq $true -and $search -eq $false) + { + $minRole = 'Application' + } + elseif ($wfe -eq $false -and $be -eq $false -and $search -eq $true) + { + $minRole = 'Search' + } + elseif ($wfe -eq $false -and $be -eq $true -and $search -eq $true) + { + $minRole = 'ApplicationWithSearch' + } + elseif ($wfe -eq $true -and $be -eq $true -and $search -eq $true) + { + $minRole = 'SingleServerFarm' + } + + $farmAccount = $Credentials | Where-Object { $_.UserName -eq $ConfigurationData.NonNodeData.ManagedAccounts.Farm } + if ($beServers -contains $Node.NodeName) + { + # All Back-end servers have to run the Central Admin + $runCentralAdmin = $true + + } + else + { + # All other servers won't run the Central Admin + $runCentralAdmin = $false + } + + if ($Node.NodeName -eq $firstBEServer) + { + $depends = '[SPProductUpdate]Language_Independant_CU' + } + else + { + WaitForAll 'WaitForFirstBEServerToComplete' + { + ResourceName = '[SPFeature]DisableMySite' + NodeName = $firstBEServer + RetryIntervalSec = 60 + RetryCount = 120 + DependsOn = '[SPProductUpdate]Language_Independant_CU' + } + $depends = '[WaitForAll]WaitForFirstBEServerToComplete' + } + + SPFarm 'SharePointFarmConfig' + { + IsSingleInstance = 'Yes' + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + FarmConfigDatabaseName = $ConfigurationData.NonNodeData.FarmConfig.ConfigDBName + AdminContentDatabaseName = $ConfigurationData.NonNodeData.FarmConfig.AdminContentDBName + Passphrase = $Passphrase + FarmAccount = $farmAccount + RunCentralAdmin = $runCentralAdmin + CentralAdministrationPort = 443 + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPProductUpdate]Language_Independant_CU' + ServerRole = $minRole + } + + SPConfigWizard "RunConfigWizard" + { + IsSingleInstance = 'Yes' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + $SharePointAdminsADGroup = $ConfigurationData.NonNodeData.DomainDetails.NetBIOSName + "\" + $ConfigurationData.NonNodeData.ActiveDirectory.SPAdmins.Name + + # Configure Farm on the first Back-End server + if ($Node.NodeName -eq $firstBEServer) + { + SPAlternateUrl "CentralAdminAAM" + { + WebAppName = $ConfigurationData.NonNodeData.CentralAdminSite.WebAppName + Zone = "Default" + Url = ("https://" + $ConfigurationData.NonNodeData.CentralAdminSite.SiteURL) + Ensure = "Present" + Internal = $false + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + xWebsite "Website" + { + Name = $ConfigurationData.NonNodeData.CentralAdminSite.WebAppName + ApplicationPool = $ConfigurationData.NonNodeData.CentralAdminSite.AppPool + BindingInfo = @( + MSFT_xWebBindingInformation + { + Protocol = 'HTTPS' + Port = '443' + CertificateThumbprint = $ConfigurationData.NonNodeData.Certificates.$($ConfigurationData.NonNodeData.CentralAdminSite.Certificate).Thumbprint.ToUpper() + CertificateStoreName = 'My' + IPAddress = '*' + Hostname = $ConfigurationData.NonNodeData.CentralAdminSite.SiteURL + } + ) + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + $farmAdmins = @() + $farmAdmins += $SharePointAdminsADGroup # SharePoint Admins AD Group + $farmAdmins += $ConfigurationData.NonNodeData.ManagedAccounts.Farm # SharePoint Farm account + + SPFarmAdministrators 'FarmAdmins' + { + IsSingleInstance = 'Yes' + Members = $farmAdmins + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPShellAdmins 'ShellAdmins' + { + IsSingleInstance = 'Yes' + Members = $SharePointAdminsADGroup + AllDatabases = $true + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPDiagnosticLoggingSettings 'ApplyDiagnosticLogSettings' + { + IsSingleInstance = 'Yes' + LogPath = $ConfigurationData.NonNodeData.Logging.ULSLogPath + LogSpaceInGB = $ConfigurationData.NonNodeData.Logging.ULSMaxSizeInGB + AppAnalyticsAutomaticUploadEnabled = $false + CustomerExperienceImprovementProgramEnabled = $false + DaysToKeepLogs = $ConfigurationData.NonNodeData.Logging.ULSDaysToKeep + DownloadErrorReportingUpdatesEnabled = $false + ErrorReportingAutomaticUploadEnabled = $false + ErrorReportingEnabled = $false + EventLogFloodProtectionEnabled = $true + EventLogFloodProtectionNotifyInterval = 5 + EventLogFloodProtectionQuietPeriod = 2 + EventLogFloodProtectionThreshold = 5 + EventLogFloodProtectionTriggerPeriod = 2 + LogCutInterval = 15 + LogMaxDiskSpaceUsageEnabled = $true + ScriptErrorReportingDelay = 30 + ScriptErrorReportingEnabled = $true + ScriptErrorReportingRequireAuth = $true + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPOutgoingEmailSettings FarmWideEmailSettings + { + WebAppUrl = ("https://" + $ConfigurationData.NonNodeData.CentralAdminSite.SiteURL) + SMTPServer = $ConfigurationData.NonNodeData.FarmConfig.OutgoingEmail.SMTPServer + FromAddress = $ConfigurationData.NonNodeData.FarmConfig.OutgoingEmail.From + ReplyToAddress = $ConfigurationData.NonNodeData.FarmConfig.OutgoingEmail.ReplyTo + UseTLS = $ConfigurationData.NonNodeData.FarmConfig.OutgoingEmail.UseTLS + SMTPPort = $ConfigurationData.NonNodeData.FarmConfig.OutgoingEmail.Port + CharacterSet = "65001" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPQuotaTemplate "Default_500MB_Quota" + { + Name = "500MB" + StorageMaxInMB = 500 + StorageWarningInMB = 450 + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + $processedAccounts = @() + $day = $ConfigurationData.NonNodeData.FarmConfig.PasswordChangeSchedule.Day + $hour = $ConfigurationData.NonNodeData.FarmConfig.PasswordChangeSchedule.Hour.ToString("00") + $pwChangeSchedule = "monthly at first $day $($hour):00:00" + foreach ($managedaccount in $ConfigurationData.NonNodeData.ManagedAccounts.GetEnumerator()) + { + if (-not $processedAccounts.Contains($managedaccount.Value)) + { + $credential = $Credentials | Where-Object { $_.UserName -eq $managedaccount.Value } + SPManagedAccount $credential.UserName + { + AccountName = $credential.UserName + Account = $credential + PreExpireDays = 2 + Schedule = $pwChangeSchedule + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + $processedAccounts += $managedaccount.Value + } + } + + SPServiceAppPool "ServiceAppPool_Services" + { + Name = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + ServiceAccount = $ConfigurationData.NonNodeData.ManagedAccounts.Services + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPManagedAccount]$($ConfigurationData.NonNodeData.ManagedAccounts.Services)" + } + + #region AppMgmtServiceApp + SPAppManagementServiceApp AppManagementServiceApp + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.AppManagement.Name + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.AppManagement.DBName + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPServiceAppPool]ServiceAppPool_Services" + } + #endregion AppMgmtServiceApp + + #region BCSServiceApp + SPBCSServiceApp BCSServiceApp + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.BCSService.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.BCSService.DBName + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPServiceAppPool]ServiceAppPool_Services" + } + #endregion BCSServiceApp + + #region MMSServiceApp + SPManagedMetaDataServiceApp "ManagedMetadataServiceApp" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.ManagedMetaDataService.Name + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.ManagedMetaDataService.DBName + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPServiceAppPool]ServiceAppPool_Services" + } + #endregion MMSServiceApp + + #region SecureStoreServiceApp + SPSecureStoreServiceApp "SecureStoreServiceApp" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.SecureStore.Name + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + AuditingEnabled = $true + AuditlogMaxSize = 30 + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.SecureStore.DBName + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPServiceAppPool]ServiceAppPool_Services" + } + #endregion SecureStoreServiceApp + + #region StateServiceApp + SPStateServiceApp "StateServiceApp" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.StateService.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.StateService.DBName + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPManagedAccount]$($ConfigurationData.NonNodeData.ManagedAccounts.Services)" + } + #endregion StateServiceApp + + #region SubscriptionSettingsServiceApp + SPSubscriptionSettingsServiceApp "SubscriptionSettingsServiceApp" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.SubscriptionSettings.Name + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.SubscriptionSettings.DBName + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPManagedAccount]$($ConfigurationData.NonNodeData.ManagedAccounts.Services)" + } + #endregion SubscriptionSettingsServiceApp + + #region UsageServiceApp + SPUsageApplication "UsageApplication" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.UsageAndHealth.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.UsageAndHealth.DBName + UsageLogCutTime = $ConfigurationData.NonNodeData.Logging.UsagePerLogInMinutes + UsageLogLocation = $ConfigurationData.NonNodeData.Logging.UsageLogPath + UsageLogMaxFileSizeKB = $ConfigurationData.NonNodeData.Logging.UsageMaxLogSizeInMB * 1024 + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + #endregion UsageServiceApp + + $hostName = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.MySiteHostLocation.Replace("http://", "").Replace("https://", "").Replace("/", "") + $site = "Site_$($hostName)" + SPUserProfileServiceApp "UserProfileServiceApp" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.Name + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + MySiteHostLocation = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.MySiteHostLocation + ProfileDBName = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.ProfileDBName + ProfileDBServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + SocialDBName = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.SocialDBName + SocialDBServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + SyncDBName = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.SyncDBName + SyncDBServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerInfr + EnableNetBIOS = $false + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPServiceAppPool]ServiceAppPool_Services", "[SPSite]$site") + } + + $UpsSyncAccount = $Credentials | Where-Object { $_.UserName -eq $ConfigurationData.NonNodeData.ManagedAccounts.UpsSync } + SPUserProfileSyncConnection "UserProfileSyncConnection" + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Name + UserProfileService = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.Name + Forest = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Forest + ConnectionCredentials = $UpsSyncAccount + UseSSL = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.UseSSL + Port = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Port + IncludedOUs = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.IncludedOUs + ExcludedOUs = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ExcludedOUs + Force = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Force + ConnectionType = $ConfigurationData.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ConnectionType + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPUserProfileServiceApp]UserProfileServiceApp" + } + + $contentDBs = @() + $spns = @() + foreach ($webAppEnum in $ConfigurationData.NonNodeData.WebApplications.GetEnumerator()) + { + # Skip to next web application if ProvisionApps=False and the current web app is Apps + if ($ConfigurationData.NonNodeData.SharePoint.ProvisionApps -eq $false -and + $webAppEnum.Key -eq 'Apps') + { + continue + } + + $webApplication = $webAppEnum.Value + + $contentDB = "ContentDB_$($webApplication.DatabaseName)" + if ($contentDBs.Contains($contentDB) -eq $true) + { + throw "Specified database is already configured!" + } + + $contentDBs += $contentDB + + $hostheader = $webApplication.Url.Replace("https://", "") + SPWebApplication $webApplication.Name + { + Name = $webApplication.Name + ApplicationPool = $webApplication.ApplicationPool + ApplicationPoolAccount = $webApplication.ApplicationPoolAccount + AllowAnonymous = $false + DatabaseName = $webApplication.DatabaseName + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerCont + WebAppUrl = $webApplication.Url + HostHeader = $hostheader + Port = $webApplication.Port + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPCacheAccounts $webApplication.Name + { + WebAppUrl = $webApplication.Url + SuperUserAlias = $ConfigurationData.NonNodeData.FarmConfig.SuperUser + SuperReaderAlias = $ConfigurationData.NonNodeData.FarmConfig.SuperReader + SetWebAppPolicy = $true + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPWebApplication]$($webApplication.Name)" + } + + SPDesignerSettings $webApplication.Name + { + WebAppUrl = $webApplication.Url + SettingsScope = "WebApplication" + AllowSharePointDesigner = $true + AllowDetachPagesFromDefinition = $false + AllowCustomiseMasterPage = $false + AllowManageSiteURLStructure = $false + AllowCreateDeclarativeWorkflow = $false + AllowSavePublishDeclarativeWorkflow = $false + AllowSaveDeclarativeWorkflowAsTemplate = $false + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPWebApplication]$($webApplication.Name)" + } + + # Create root Path based site collection + $pathBasedRootSC = $webApplication.PathBasedRootSiteCollection + $contentDB = "ContentDB_$($pathBasedRootSC.ContentDatabase)" + $depends = "[SPWebApplication]$($webApplication.Name)" + + if ($contentDBs.Contains($contentDB) -eq $false) + { + SPContentDatabase $contentDB + { + Name = $pathBasedRootSC.ContentDatabase + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerCont + WebAppUrl = $webApplication.Url + Enabled = $true + WarningSiteCount = 2000 + MaximumSiteCount = 5000 + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPWebApplication]$($webApplication.Name)" + } + + $contentDBs += $contentDB + $depends = "[SPContentDatabase]$($contentDB)" + } + + # Determine hostname, used for configuring SPN + $hostName = $pathBasedRootSC.Url.Replace("http://", "").Replace("https://", "").Replace("/", "") + + $webAppAccount = Split-Path -Path $webApplication.ApplicationPoolAccount -Leaf + + $spn = "HTTP/$hostName" + if ($spns -notcontains $spn) + { + $spns += $spn + ADServicePrincipalName "SPN_$spn" + { + ServicePrincipalName = $spn + Account = $webAppAccount + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[WindowsFeature]ADPowerShell' + } + } + + SPSite $pathBasedRootSC.Url + { + Url = $pathBasedRootSC.Url + OwnerAlias = $webApplication.OwnerAlias + ContentDatabase = $pathBasedRootSC.ContentDatabase + Name = $pathBasedRootSC.Name + Template = $pathBasedRootSC.Template + Language = $pathBasedRootSC.Language + PsDscRunAsCredential = $InstallAccount + DependsOn = $depends + } + + # Create Host Header site collections + foreach ($hostNamedSiteCollection in $webApplication.HostNamedSiteCollections) + { + # Determine hostname, used for configuring SPN + $hostName = $hostNamedSiteCollection.Url.Replace("http://", "").Replace("https://", "").Replace("/", "") + + $spn = "HTTP/$hostName" + if ($spns -notcontains $spn) + { + ADServicePrincipalName "SPN_$spn" + { + ServicePrincipalName = $spn + Account = $webAppAccount + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[WindowsFeature]ADPowerShell' + } + } + + $site = "Site_$($hostName)" + + $contentDB = "ContentDB_$($hostNamedSiteCollection.ContentDatabase)" + $depends = "[SPWebApplication]$($webApplication.Name)" + + if ($contentDBs.Contains($contentDB) -eq $false) + { + SPContentDatabase $contentDB + { + Name = $hostNamedSiteCollection.ContentDatabase + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerCont + WebAppUrl = $webApplication.Url + Enabled = $true + WarningSiteCount = 2000 + MaximumSiteCount = 5000 + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = "[SPWebApplication]$($webApplication.Name)" + } + + $contentDBs += $contentDB + $depends = "[SPContentDatabase]$contentDB" + } + + SPSite $site + { + Url = $hostNamedSiteCollection.Url + OwnerAlias = $webApplication.OwnerAlias + ContentDatabase = $hostNamedSiteCollection.ContentDatabase + HostHeaderWebApplication = $webApplication.Url + Name = $hostNamedSiteCollection.Name + Template = $hostNamedSiteCollection.Template + Language = $hostNamedSiteCollection.Language + PsDscRunAsCredential = $InstallAccount + DependsOn = $depends + } + } + + SPWebAppAuthentication "WebAppAuthentication_$($webApplication.Name)" + { + WebAppUrl = $webApplication.Url + Default = @( + MSFT_SPWebAppAuthenticationMode + { + AuthenticationMethod = "WindowsAuthentication" + WindowsAuthMethod = "Kerberos" + } + ) + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPWebAppGeneralSettings "GeneralSettings_$($webApplication.Name)" + { + WebAppUrl = $webApplication.Url + TimeZone = 4 # (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna + SelfServiceSiteCreationEnabled = $false + MaximumUploadSize = 250 + RecycleBinEnabled = $true + DefaultQuotaTemplate = "500MB" + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPWebAppThrottlingSettings "Throttling_$($webApplication.Name)" + { + WebAppUrl = $webApplication.Url + ListViewLookupThreshold = 8 + HappyHourEnabled = $true + HappyHour = MSFT_SPWebApplicationHappyHour + { + Hour = 6 + Minute = 0 + Duration = 2 + } + ChangeLogEnabled = $true + ChangeLogExpiryDays = 60 + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPBlobCacheSettings "BlobCacheSettings_$($webApplication.Name)" + { + WebAppUrl = $webApplication.Url + Zone = 'Default' + EnableCache = $true + Location = $webApplication.BlobCacheFolder + MaxSizeInGB = $webApplication.BlobCacheSize + FileTypes = $webApplication.BlobCacheFileTypes + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPWebAppPermissions "WebAppPermissions_$($webApplication.Name)" + { + WebAppUrl = $webApplication.Url + AllPermissions = $true + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + if ($webApplication.Name -eq $ConfigurationData.NonNodeData.WebApplications.Content.Name) + { + SPFeature "EnableFeature_DocumentManagement_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'DocumentManagement' + FeatureScope = 'WebApplication' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPFeature "EnableFeature_SPSearch_Enterprise_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'OSearchEnhancedFeature' + FeatureScope = 'WebApplication' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPFeature "EnableFeature_SPSearch_Standard_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'OSearchBasicFeature' + FeatureScope = 'WebApplication' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPFeature "EnableFeature_SP_Standard_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'BaseWebApplication' + FeatureScope = 'WebApplication' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPFeature "DisableFeature_InternetFacingApps_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'IfeDependentApps' + FeatureScope = 'WebApplication' + Ensure = 'Absent' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPFeature "DisableFeature_SP_Enterprise_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'PremiumWebApplication' + FeatureScope = 'WebApplication' + Ensure = 'Absent' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + SPFeature "DisableFeature_VideoProcessin_$($webApplication.Name)" + { + Url = $webApplication.Url + Name = 'VideoProcessing' + FeatureScope = 'WebApplication' + Ensure = 'Absent' + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)") + } + + if ($ConfigurationData.NonNodeData.SharePoint.ProvisionApps -eq $true) + { + SPAppStoreSettings "AppStoreSettings_$($webApplication.Name)" + { + WebAppUrl = $webApplication.Url + AllowAppPurchases = $ConfigurationData.NonNodeData.FarmConfig.AppsSettings.AllowAppPurchases + AllowAppsForOffice = $ConfigurationData.NonNodeData.FarmConfig.AppsSettings.AllowAppPurchases + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPWebApplication]$($webApplication.Name)", "[SPAppCatalog]Configure_AppCatalog") + } + } + } + } + + SPFeature "DisableMySite" + { + Name = "MySite" + Url = "N/A" + FeatureScope = "Farm" + Ensure = "Absent" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + if ($ConfigurationData.NonNodeData.SharePoint.ProvisionApps -eq $true) + { + $appCatalogUrl = ($ConfigurationData.NonNodeData.WebApplications.Content.HostNamedSiteCollections | Where-Object { $_.Template -eq "APPCATALOG#0" }).Url + $appCatalogHostName = $appCatalogUrl.Replace("http://", "").Replace("https://", "").Replace("/", "") + SPAppCatalog "Configure_AppCatalog" + { + SiteUrl = $appCatalogUrl + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPSite]Site_$($appCatalogHostName)") + } + + SPAppDomain "ConfigureAppDomain" + { + AppDomain = $ConfigurationData.NonNodeData.FarmConfig.AppsSettings.AppDomain + Prefix = $ConfigurationData.NonNodeData.FarmConfig.AppsSettings.Prefix + PsDscRunAsCredential = $InstallAccount + DependsOn = @("[SPAppCatalog]Configure_AppCatalog") + } + } + } + + # Configure all Back-End servers + if ($beServers -contains $Node.NodeName) + { + $blobCacheFolders = $ConfigurationData.NonNodeData.WebApplications.GetEnumerator() | ForEach-Object { + $_.Value.BlobCacheFolder + } | Sort-Object -Unique + + foreach ($folder in $blobCacheFolders) + { + $name = $folder -replace ":", "" -replace "\\", "_" + File "BlobCacheFolder_$name" + { + Type = "Directory" + DestinationPath = $folder + Ensure = "Present" + Credential = $InstallAccount + } + } + + SPServiceInstance 'BusinessConnectivityServiceInstance' + { + Name = "Business Data Connectivity Service" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'CentralAdministrationServiceInstance' + { + Name = "Central Administration" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'ManagedMetadataServiceInstance' + { + Name = "Managed Metadata Web Service" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'IncomingEmailServiceInstance' + { + Name = "Microsoft SharePoint Foundation Incoming E-Mail" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'SubscriptionSettingsServiceInstance' + { + Name = "Microsoft SharePoint Foundation Subscription Settings Service" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'WebApplicationServiceInstance' + { + Name = "Microsoft SharePoint Foundation Web Application" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'WebAppTimerServiceInstance' + { + Name = "Microsoft SharePoint Foundation Workflow Timer Service" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'SecureStoreServiceInstance' + { + Name = "Secure Store Service" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'UserProfileServiceInstance' + { + Name = "User Profile Service" + Ensure = "Present" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + } + + # Configure all Search servers + if ($searchServers -contains $Node.NodeName) + { + File "IndexPartitionRootDir_$($Node.NodeName)" + { + Type = "Directory" + DestinationPath = $ConfigurationData.NonNodeData.ServiceApplications.SearchService.IndexPartitionRootDirectory + Ensure = "Present" + Credential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + $path = $ConfigurationData.NonNodeData.ServiceApplications.SearchService.IndexPartitionRootDirectory + $account = $ConfigurationData.NonNodeData.ManagedAccounts.Search + Script 'Set_Folder_Permissions_IndexPartitionRootDir' + { + GetScript = { + $permissions = @{ + Account = $using:account + FileSystemRights = $null + AccessControlType = $null + InheritanceFlags = $null + PropagationFlags = $null + } + + $acl = Get-Acl -Path $using:path + $userAcl = $acl.Access | Where-Object { $_.IdentityReference -eq $using:account } + if ($null -eq $userAcl) + { + return @{ Result = ($permissions | format-list * -force | out-string) } + } + elseif ($userAcl.Count -gt 1) + { + return @{ Result = ($permissions | format-list * -force | out-string) } + } + else + { + $permissions.FileSystemRights = $userAcl.FileSystemRights.ToString() + $permissions.AccessControlType = $userAcl.AccessControlType.ToString() + $permissions.InheritanceFlags = $userAcl.InheritanceFlags.ToString() + $permissions.PropagationFlags = $userAcl.PropagationFlags.ToString() + return @{ Result = ($permissions | format-list * -force | out-string) } + } + } + SetScript = { + $acl = Get-Acl $using:path + + $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($using:account, 'Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow') + + $acl.SetAccessRule($AccessRule) + + Set-Acl -Path $using:path -AclObject $acl + } + TestScript = { + $acl = Get-Acl -Path $using:path + $userAcl = $acl.Access | Where-Object { $_.IdentityReference -eq $using:account } + if ($null -eq $userAcl) + { + return $false + } + elseif ($userAcl.Count -gt 1) + { + return $false + } + else + { + if ($userAcl.FileSystemRights -eq 'Modify, Synchronize' -and + $userAcl.AccessControlType -eq 'Allow' -and + $userAcl.InheritanceFlags -eq 'ContainerInherit, ObjectInherit' -and + $userAcl.PropagationFlags -eq 'None') + { + return $true + } + } + return $false + } + DependsOn = "[File]IndexPartitionRootDir_$($Node.NodeName)" + } + } + + # Configure Search on the first Backend Search server + if ($Node.NodeName -eq $firstSearchBEServer) + { + WaitForAll 'WaitForServiceAppPool_Services' + { + NodeName = $firstBEServer + ResourceName = '[SPServiceAppPool]ServiceAppPool_Services' + RetryIntervalSec = 60 + RetryCount = 60 + } + + $servicesAccount = $Credentials | Where-Object { $_.UserName -eq $ConfigurationData.NonNodeData.ManagedAccounts.Search } + $crawlAccount = $Credentials | Where-Object { $_.UserName -eq $ConfigurationData.NonNodeData.ServiceApplications.SearchService.DefaultContentAccessAccount } + + SPSearchServiceApp 'SearchServiceApp' + { + Name = $ConfigurationData.NonNodeData.ServiceApplications.SearchService.Name + DatabaseServer = $ConfigurationData.NonNodeData.DomainDetails.DBServerSear + DatabaseName = $ConfigurationData.NonNodeData.ServiceApplications.SearchService.DBName + ApplicationPool = $ConfigurationData.NonNodeData.ApplicationPools.ServiceApplicationPools.Name + DefaultContentAccessAccount = $crawlAccount + SearchCenterUrl = $ConfigurationData.NonNodeData.ServiceApplications.SearchService.SearchCenterUrl + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[WaitForAll]WaitForServiceAppPool_Services' + } + + SPSearchTopology 'LocalSearchTopology' + { + ServiceAppName = $ConfigurationData.NonNodeData.ServiceApplications.SearchService.Name + Admin = $searchBEServers + Crawler = $searchBEServers + ContentProcessing = $searchBEServers + AnalyticsProcessing = $searchBEServers + QueryProcessing = $searchFEServers + IndexPartition = $searchFEServers + FirstPartitionDirectory = "$($ConfigurationData.NonNodeData.ServiceApplications.SearchService.IndexPartitionRootDirectory)\0" + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPSearchServiceApp]SearchServiceApp' + } + + SPSearchServiceSettings 'SearchServiceSettings' + { + IsSingleInstance = 'Yes' + PerformanceLevel = $ConfigurationData.NonNodeData.FarmConfig.SearchSettings.PerformanceLevel + ContactEmail = $ConfigurationData.NonNodeData.FarmConfig.SearchSettings.ContactEmail + WindowsServiceAccount = $servicesAccount + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPSearchTopology]LocalSearchTopology' + } + } + + # Configure all Front-End servers + if ($feServers -contains $Node.NodeName) + { + # If server also is specified as BE server, skip this section. Services are already started. + if ($beServers -notcontains $Node.NodeName) + { + $blobCacheFolders = $ConfigurationData.NonNodeData.WebApplications.GetEnumerator() | ForEach-Object { + $_.Value.BlobCacheFolder + } | Sort-Object -Unique + + foreach ($folder in $blobCacheFolders) + { + $name = $folder -replace ":", "" -replace "\\", "_" + File "BlobCacheFolder_$name" + { + Type = "Directory" + DestinationPath = $folder + Ensure = "Present" + Credential = $InstallAccount + } + } + + SPServiceInstance 'BusinessConnectivityServiceInstance' + { + Name = 'Business Data Connectivity Service' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'ManagedMetadataServiceInstance' + { + Name = 'Managed Metadata Web Service' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'SubscriptionSettingsServiceInstance' + { + Name = 'Microsoft SharePoint Foundation Subscription Settings Service' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'WebApplicationServiceInstance' + { + Name = 'Microsoft SharePoint Foundation Web Application' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'SecureStoreServiceInstance' + { + Name = 'Secure Store Service' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + SPServiceInstance 'UserProfileServiceInstance' + { + Name = 'User Profile Service' + Ensure = 'Present' + PsDscRunAsCredential = $InstallAccount + DependsOn = '[SPFarm]SharePointFarmConfig' + } + } + + $processedAppPools = @() + foreach ($webAppEnum in $ConfigurationData.NonNodeData.WebApplications.GetEnumerator()) + { + $internalName = $webAppEnum.Name + $webApplication = $webAppEnum.Value + + WaitForAny "WaitForWebApplication_$($webApplication.Name)" + { + NodeName = $firstBEServer + ResourceName = "[SPWebApplication]$($webApplication.Name)" + RetryIntervalSec = 60 + RetryCount = 60 + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + $xWebsite = "xWebsite_$($webApplication.Name)" + + xWebsite $xWebsite + { + Name = $webApplication.Name + BindingInfo = @( + $hostName = $webApplication.URL.Replace('http://', '').Replace('https://', '').Replace('/', '').Replace('root', '*') + MSFT_xWebBindingInformation + { + Port = 443 + Protocol = 'HTTPS' + IPAddress = $node.IPAddress.$($internalName) + CertificateThumbprint = $ConfigurationData.NonNodeData.Certificates.$($webApplication.Certificate).Thumbprint.ToUpper() + CertificateStoreName = $webApplication.CertificateStoreName + } + ) + DependsOn = "[WaitForAny]WaitForWebApplication_$($webApplication.Name)" + } + + if ($processedAppPools -notcontains $webApplication.ApplicationPool) + { + xWebAppPool "AppPoolSettings_$($webApplication.ApplicationPool)" + { + Name = $webApplication.ApplicationPool + Ensure = 'Present' + State = 'Started' + autoStart = $true + restartSchedule = @("02:$(Get-Random -Maximum 59):00") + DependsOn = "[WaitForAny]WaitForWebApplication_$($webApplication.Name)" + } + $processedAppPools += $webApplication.ApplicationPool + } + } + + WaitForAll 'WaitForServicesManagedAccount' + { + NodeName = $firstBEServer + ResourceName = "[SPManagedAccount]$($ConfigurationData.NonNodeData.ManagedAccounts.Services)" + RetryIntervalSec = 60 + RetryCount = 60 + DependsOn = '[SPFarm]SharePointFarmConfig' + } + + $servicesAccount = $Credentials | Where-Object { $_.UserName -eq $ConfigurationData.NonNodeData.ManagedAccounts.Services } + + SPDistributedCacheService "DistributedCacheService_$($Node.NodeName)" + { + Name = 'Distributed Cache Service' + Ensure = 'Present' + CacheSizeInMB = 1024 + ServiceAccount = $servicesAccount.UserName + CreateFirewallRules = $true + ServerProvisionOrder = $distributedCacheServers + PsDscRunAsCredential = $InstallAccount + DependsOn = '[WaitForAll]WaitForServicesManagedAccount' + } + } + + #********************************************************** + # IIS clean up + # + # This section stop all default sites and application + # pools from IIS as they are not required + #********************************************************** + + xWebAppPool 'DisableDotNet2Pool' + { + Name = '.NET v2.0'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + xWebAppPool 'DisableDotNet2ClassicPool' + { + Name = '.NET v2.0 Classic'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + xWebAppPool 'DisableDotNet45Pool' + { + Name = '.NET v4.5'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + xWebAppPool 'DisableDotNet45ClassicPool' + { + Name = '.NET v4.5 Classic'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + xWebAppPool 'DisableClassicDotNetPool' + { + Name = 'Classic .NET AppPool'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + xWebAppPool 'DisableDefaultAppPool' + { + Name = 'DefaultAppPool'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + xWebSite 'DisableDefaultWebSite' + { + Name = 'Default Web Site'; State = 'Stopped'; DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + + xIisLogging 'ConfigureIISLogging' + { + LogPath = $ConfigurationData.NonNodeData.Logging.IISLogPath + Logflags = @('Date', 'Time', 'ServerIP', 'Method', 'UriStem', 'UriQuery', 'ServerPort', 'UserName', 'ClientIP', 'UserAgent', 'Referer', 'HttpStatus', 'HttpSubStatus', 'Win32Status', 'TimeTaken') + LoglocalTimeRollover = $true + LogPeriod = 'Daily' + LogFormat = 'W3C' + DependsOn = '[SPInstallPrereqs]Install_SP_Prereqs' + } + } + #endregion + } +} + +##### DSC COMPILATION ##### +Write-Host 'Compiling DSC Configuration for SharePoint Servers' -ForegroundColor Green +if ($null -eq $Datafile) +{ + Write-Host '[ERROR] Datafile variable not specified. Have you ran the PrepVariables script?' -ForegroundColor Red + Exit +} + +Write-Host 'Checking Building Block versions:' -ForegroundColor DarkGray +$dataFileVersion = [System.Version]$DataFile.NonNodeData.BuildingBlock.Version +Write-Host " - Data file version : $($dataFileVersion.ToString())" -ForegroundColor DarkGray +Write-Host " - Script version : $($buildingBlockVersion.ToString())" -ForegroundColor DarkGray +if ($dataFileVersion -eq $buildingBlockVersion) +{ + Write-Host 'Versions equal, proceeding...' -ForegroundColor DarkGray + + foreach ($node in $DataFile.AllNodes) + { + if ($node.CertificateFile -eq '') + { + Write-Host "Node $($node.NodeName) does not have a valid Certificate File populated, cancelling compilation!" -ForegroundColor Red + exit + } + + if ($node.Thumbprint -eq '') + { + Write-Host "Node $($node.NodeName) does not have a valid Thumbprint populated, cancelling compilation!" -ForegroundColor Red + exit + } + } + + if ($ConfigPathFull -and (Test-Path $ConfigPathFull)) + { + $outputPath = Join-Path $ConfigPathFolder '\Deploy_SP' + Deploy_SP -ConfigurationData $ConfigPathFull ` + -InstallAccount $InstallAccount ` + -PassPhrase $PassPhrase ` + -CertificatePassword $CertPassword ` + -Credentials $credentials ` + -OutputPath $outputPath + } + else + { + Write-Host 'Configuration Data file unknown, did you run PrepVariables.ps1?' -ForegroundColor Red + } +} +else +{ + Write-Host 'Versions do not match, please check the building block versions. Quiting!' -ForegroundColor Red + break +} + +<# +# Deploy MOF files +Start-DscConfiguration -Path '.\Deploy_SP' -Verbose -Wait -force + +$SPSrv1 = Test-DscConfiguration -Computername SPSrv1 -Verbose -Detailed +$SPSrv2 = Test-DscConfiguration -Computername SPSrv2 -Verbose -Detailed +$SPSrv3 = Test-DscConfiguration -Computername SPSrv3 -Verbose -Detailed +$SPSrv4 = Test-DscConfiguration -Computername SPSrv4 -Verbose -Detailed + +#> diff --git a/SharePointDsc/FarmDeployment/PrepServers.ps1 b/SharePointDsc/FarmDeployment/PrepServers.ps1 new file mode 100644 index 000000000..89267cacf --- /dev/null +++ b/SharePointDsc/FarmDeployment/PrepServers.ps1 @@ -0,0 +1,370 @@ +#region ##### FUNCTIONS ##### +function WriteLog +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Message + ) + + $date = Get-Date -f 'yyy-MM-dd HH:mm:ss' + Write-Output "$date - $Message" +} +#endregion FUNCTIONS + +#region INITIALIZE VARIABLES +$buildingBlockVersion = [System.Version]'1.0.0' +$ResSourceLocation = 'D:\SPSources\2019\Resources' +$certLocation = 'D:\SPCertificates' +$defaultFolder = $PSScriptRoot +$serverThumbprints = @{} +#endregion INITIALIZE VARIABLES + +#region SCRIPT BODY +WriteLog -Message 'Starting PrepServers script' +WriteLog -Message "Running as $(&whoami)" + +Set-Location $defaultFolder + +#region Check Prerequisites +WriteLog -Message 'Checking prerequisites' +WriteLog -Message ' Checking Datafile variable' + +WriteLog -Message ' Checking Building Block versions:' +$dataFileVersion = [System.Version]$DataFile.NonNodeData.BuildingBlock.Version +WriteLog -Message " - Data file version : $($dataFileVersion.ToString())" +WriteLog -Message " - Script version : $($buildingBlockVersion.ToString())" +if ($dataFileVersion -eq $buildingBlockVersion) +{ + WriteLog -Message ' Versions equal, proceeding...' +} +else +{ + WriteLog -Message ' Versions do not match, please check the building block versions. Quiting!' + Exit 10 +} + +if ($null -eq $Datafile) +{ + WriteLog -Message ' [ERROR] Datafile variable not specified. Have you ran the PrepVariables script?' + Exit 50 +} + +WriteLog -Message ' Checking specified ResSourceLocation path' +if ((Test-Path -Path $ResSourceLocation) -eq $false) +{ + WriteLog -Message " [ERROR] Specified Resource path does not exist: $ResSourceLocation" + Exit 100 +} +#endregion Check Prerequisites + +#region Deploy DSC Encryption certificates +WriteLog -Message 'Starting Check/Deploy DSC Encryption Certificates to all servers' + +$servers = $DataFile.AllNodes.NodeName | Where-Object { $_ -ne '*' } + +foreach ($server in $servers) +{ + WriteLog -Message " Processing server $server" + $serverThumbprints.$server = @{} + + $session = New-PSSession -ComputerName $server + + $lcm = Invoke-Command -Session $session -ScriptBlock { + return (Get-DscLocalConfigurationManager) + } + + $foundError = $false + $serverThumbprint = '' + + if ($null -eq $lcm.CertificateID) + { + WriteLog -Message ' No certificate configured. Generating new certificate.' + $certificate = Invoke-Command -Session $session -ScriptBlock { + $cert = New-SelfSignedCertificate -Type DocumentEncryptionCertLegacyCsp ` + -DnsName 'DSCNode Document Encryption' ` + -HashAlgorithm SHA256 ` + -KeyExportPolicy NonExportable ` + -NotAfter (Get-Date).AddYears(10) + return $cert + } + $serverThumbprint = $certificate.Thumbprint + WriteLog -Message " Certificate created with thumbprint $($certificate.Thumbprint)" + } + else + { + WriteLog -Message " Certificate configured: $($lcm.CertificateID)" + WriteLog -Message ' Checking presence of certificate.' + + $certificate = Invoke-Command -Session $session -ArgumentList $lcm.CertificateID -ScriptBlock { + $thumbprint = $args[0] + if (Test-Path -Path "Cert:\LocalMachine\My\$thumbprint") + { + # Checking cert + $cert = Get-ChildItem "Cert:\LocalMachine\My\$thumbprint" + } + else + { + return $cert = $null + } + return $cert + } + + if ($null -ne $certificate) + { + WriteLog -Message ' Certificate exists, checking validity.' + $certValidity = $certificate.NotAfter - (Get-Date) + if ($certValidity.TotalDays -le 1826) + { + Add-Type -AssemblyName System.Windows.Forms + $buttons = [System.Windows.Forms.MessageBoxButtons]::YesNo + $choice = [System.Windows.Forms.MessageBox]::Show("Configured DSC certificate valid for less than five years.`n`nCertificate valid until: $($certificate.NotAfter)`n`nDo you want to use this certificate?", " WARNING", $buttons) + if ($choice -eq 'Yes') + { + WriteLog -Message ' Approved. Using certificate.' + } + else + { + WriteLog -Message ' Denied! Not using specified certificate. Skipping to next server!' + continue + } + } + + if ($null -eq ($certificate.Extensions | Where-Object { $_.KeyUsages -match 'DataEncipherment' -or $_.KeyUsages -match 'KeyEncipherment' })) + { + Write-Host 'ERROR: Configured DSC certificate does not have DataEncipherment or KeyEncipherment extensions configured!' -ForegroundColor Red + $foundError = $true + } + + if ($null -ne ($certificate.Extensions | Where-Object { $_.KeyUsages -match 'Digital Signature' })) + { + Write-Host 'ERROR: Configured DSC certificate should not have Digital Signature extensions configured!' -ForegroundColor Red + $foundError = $true + } + + if ($null -eq ($certificate.EnhancedKeyUsageList | Where-Object { $_.FriendlyName -match 'Document Encryption' })) + { + Write-Host 'ERROR: Configured DSC certificate does not have "Document Encryption" Enhanced Key Usage configured!' -ForegroundColor Red + $foundError = $true + } + + if ($null -ne ($certificate.EnhancedKeyUsageList | Where-Object { $_.FriendlyName -match 'Client Authentication' -or $_.FriendlyName -match 'Server Authentication' })) + { + Write-Host 'ERROR: Configured DSC certificate should not have "Client Authentication" or "Server Authentication" Enhanced Key Usage configured!' -ForegroundColor Red + $foundError = $true + } + + $serverThumbprint = $lcm.CertificateID + } + else + { + WriteLog -Message " Configured DSC certificate with ID $($lcm.CertificateID) not found" + $foundError = $true + } + } + + if ($foundError -eq $false) + { + $filename = "$server.cer" + Invoke-Command -Session $session -ScriptBlock { + $null = $cert | Export-Certificate -FilePath "C:\$($env:COMPUTERNAME).cer" -Force + } + WriteLog -Message ' Public certificate exported' + + Copy-Item -FromSession $session -Path "C:\$filename" -Destination $certLocation + Remove-Item "\\$server\c$\$filename" + + $serverThumbprints.$server.Thumbprint = $serverThumbprint + $serverThumbprints.$server.CertificateFile = $filename + } + else + { + Write-Host 'Encountered error in certificate check, skipping certificate.' + $serverThumbprints.$server = 'ERROR' + } + + Invoke-Command -Session $session -ScriptBlock { if ( [int](Get-Item -Path WSMan:\localhost\MaxEnvelopeSizeKb).Value -lt 1024 ) { Set-Item -Path WSMan:\localhost\MaxEnvelopeSizeKb 1024 } } + + Remove-PSSession -Session $session +} + +WriteLog -Message ' Reloading updated PSD1 file' +$global:DataFile = Import-PowerShellDataFile $ConfigPathFull + +WriteLog -Message 'Completed Check/Deploy DSC Encryption Certificates to all servers' +#endregion Deploy DSC Encryption certificates + +#region Update CertificateFiles and Thumbprints psd1 file +WriteLog -Message 'Updating PSD1 file with DSC Encryption Certificates information' +$content = Get-Content -Path $ConfigPathFull + +$tempFile = Join-Path -Path $env:TEMP -ChildPath 'SharePoint.psd1' + +if ((Test-Path -Path $tempFile) -eq $true) +{ + Remove-Item -Path $tempFile -Force +} + +$server = '' +foreach ($line in $content) +{ + $newline = $line + if ($line -match 'NodeName\s*=\s*"(\w*)"') + { + $newServer = $false + $server = $Matches[1] + } + + if ($line -match 'NonNodeData') + { + $newServer = $true + $server = '' + } + + if ($server -ne '') + { + if ($line -match 'Thumbprint\s*=\s*["''][A-z0-9<>]*["'']') + { + if ($serverThumbprints.ContainsKey($server) -and + $serverThumbprints.$server.ContainsKey('Thumbprint') -and + $serverThumbprints.$server.Thumbprint -ne '') + { + $thumbprint = $serverThumbprints.$server.Thumbprint + $newline = $line -replace '["''][A-z0-9<>]*["'']', "'$thumbprint'" + WriteLog " Replacing thumbprint for server $server to $($serverThumbprints.$server.Thumbprint)!" + } + else + { + WriteLog " [ERROR] Cannot replace thumbprint for server $server!" + } + } + + if ($line -match 'CertificateFile\s*=\s*') + { + if ($serverThumbprints.ContainsKey($server) -and + $serverThumbprints.$server.ContainsKey('CertificateFile') -and + $serverThumbprints.$server.Thumbprint -ne '') + { + $certificateFile = (Join-Path -Path 'C:\SPCertificates' -ChildPath $serverThumbprints.$server.CertificateFile) + $regex = '["''][A-z09<>:\._]*["'']' + $newline = $line -replace $regex, "'$certificateFile'" + WriteLog " Replacing CertificateFile for server $server to $certificateFile!" + } + else + { + WriteLog " [ERROR] Cannot replace CertificateFile for server $server!" + } + } + } + + if ($line -match '@{') + { + $newServer = $true + } + Add-Content -Value $newline -Path $tempFile +} +Copy-Item -Path $tempFile -Destination $ConfigPathFull -Force + +Remove-Item -Path $tempFile -Force + +WriteLog -Message ' Reloading updated PSD1 file' +$global:DataFile = Import-PowerShellDataFile $ConfigPathFull + +WriteLog -Message 'Completed updating PSD1 file with DSC Encryption Certificates information' +#endregion Update CertificateFiles and Thumbprints psd1 file + +#region Deploy DSC Modules +WriteLog -Message 'Deploying required DSC modules to all servers' + +# Get server collections based on Role property +$camServers = ($DataFile.AllNodes | Where-Object { $_.Role -eq 'CAMApps' }).NodeName +$spServers = ($DataFile.AllNodes | Where-Object { $_.Role -eq 'SharePoint' }).NodeName + +# Deployment Server (First SharePoint Backend server) +WriteLog ' Copying resources to deployment server' +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\ActiveDirectoryDsc') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\PsDscResources') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\xCredSSP') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\xWebAdministration') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\CertificateDsc') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\ComputerManagementDsc') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\SharePointDsc') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\SQLServerDsc') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force +Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\SQLServer') -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force + +WriteLog ' Unblock resource folders on deployment server' +Get-ChildItem -Path 'C:\Program Files\WindowsPowerShell\Modules' -Recurse | Unblock-File + +# SharePoint Server +WriteLog ' Copying resources to SharePoint servers' +foreach ($sp in $spServers) +{ + WriteLog " Copying resources to SharePoint server: $sp" + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\xCredSSP') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\xWebAdministration') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\CertificateDsc') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\ComputerManagementDsc') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\SharePointDsc') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\SQLServerDsc') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\SQLServer') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\ActiveDirectoryDsc') -Destination (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + + WriteLog " Unblock resource folders on SharePoint server: $sp" + Get-ChildItem -Path (Join-Path -Path "\\$sp" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse | Unblock-File +} + +# CAM Server +WriteLog ' Copying resources to CAM servers' +foreach ($cam in $camServers) +{ + WriteLog " Copying resources to CAM server: $cam" + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\xWebAdministration') -Destination (Join-Path -Path "\\$cam" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\CertificateDsc') -Destination (Join-Path -Path "\\$cam" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\ComputerManagementDsc') -Destination (Join-Path -Path "\\$cam" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + Copy-Item -Path (Join-Path -Path $ResSourceLocation -ChildPath '\PsDscResources') -Destination (Join-Path -Path "\\$cam" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse -Container -Force + + WriteLog " Unblock resource folders on CAM server: $cam" + Get-ChildItem -Path (Join-Path -Path "\\$cam" -ChildPath 'C$\Program Files\WindowsPowerShell\Modules') -Recurse | Unblock-File +} +WriteLog -Message 'Completed deploying required DSC modules to all servers' +#endregion Deploy DSC Modules + +#region Compile and deploy LCM configuration MOF file +WriteLog -Message 'Configure LCM on all servers' + +. .\PrepServersConfig.ps1 + +foreach ($node in $DataFile.AllNodes) +{ + if ($node.CertificateFile -eq '') + { + WriteLog " [ERROR] Node $($node.NodeName) does not have a valid Certificate File populated, cancelling compilation!" + exit + } + + if ($node.Thumbprint -eq '') + { + WriteLog " [ERROR] Node $($node.NodeName) does not have a valid Thumbprint populated, cancelling compilation!" + exit + } +} + +if ($null -ne $ConfigPathFull -and (Test-Path $ConfigPathFull) -eq $true) +{ + $outputPath = Join-Path -Path $ConfigPathFolder -ChildPath 'Deploy_PrepServers' + Deploy_PrepServers -ConfigurationData $ConfigPathFull ` + -OutputPath $outputPath | Out-Null + + Set-DscLocalConfigurationManager -Path $outputPath -Verbose -Force +} +else +{ + Write-Output 'Configuration Data file unknown, did you run PrepVariables.ps1?' + Exit 1000 +} +WriteLog -Message 'Completed configuring LCM on all servers' +#endregion Compile and deploy LCM configuration MOF file + +WriteLog -Message 'Completed PrepServers script' +#endregion SCRIPT BODY diff --git a/SharePointDsc/FarmDeployment/PrepServersConfig.ps1 b/SharePointDsc/FarmDeployment/PrepServersConfig.ps1 new file mode 100644 index 000000000..7444635c9 --- /dev/null +++ b/SharePointDsc/FarmDeployment/PrepServersConfig.ps1 @@ -0,0 +1,17 @@ +Configuration Deploy_PrepServers +{ + param + ( + ) + + Import-DscResource -ModuleName PSDesiredStateConfiguration + + node $AllNodes.NodeName + { + LocalConfigurationManager + { + RebootNodeIfNeeded = $true + CertificateId = $Node.Thumbprint.ToUpper() + } + } +} diff --git a/SharePointDsc/FarmDeployment/PrepVariables.ps1 b/SharePointDsc/FarmDeployment/PrepVariables.ps1 new file mode 100644 index 000000000..efb192dbe --- /dev/null +++ b/SharePointDsc/FarmDeployment/PrepVariables.ps1 @@ -0,0 +1,164 @@ +#Requires -Version 5.1 + +##### GENERIC VARIABLES ##### +$defaultFolder = $PSScriptRoot +$buildingBlockVersion = [System.Version]'1.0.0' + +##### SUPPORTING FUNCTIONS ##### +function Test-Credential +{ + [OutputType([Bool])] + Param ( + [Parameter( + Mandatory = $true, + ValueFromPipeLine = $true, + ValueFromPipelineByPropertyName = $true + )] + [Alias( + 'PSCredential' + )] + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential, + + [Parameter()] + [String] + $Domain = $Credential.GetNetworkCredential().Domain + ) + begin + { + Add-Type -assemblyname system.DirectoryServices.accountmanagement + $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Domain) + } + process + { + $account = Split-Path -Path $credential.UserName -Leaf + return $DS.ValidateCredentials($account, $credential.GetNetworkCredential().password) + } +} + +function Get-Accounts +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Account, + + [Parameter(Mandatory = $true)] + [System.String] + $Description + ) + + if ($null -eq $global:credentials.UserName -or + -not $global:credentials.UserName.Contains($Account)) + { + $credential = Get-Credential $Account -Message $Description + if (Test-Credential -Credential $credential) + { + $global:credentials += $credential + } + else + { + Write-Error "Incorrect credential" + } + } +} + +##### SCRIPT START ##### + +Clear-Host +Write-Host 'Preparing variables for SharePoint deployment' -ForegroundColor Green + +# Dialog for selecting PSD input file +Add-Type -AssemblyName System.Windows.Forms +$dialog = New-Object System.Windows.Forms.OpenFileDialog +$dialog.InitialDirectory = $defaultFolder +$dialog.Title = "Please select the DSC data file" +$dialog.Filter = "DSC Config (*.psd1) | *.psd1" +$result = $dialog.ShowDialog() + +if ($result -eq "OK") +{ + Write-Host "Processing file: $($dialog.FileName)" -ForegroundColor DarkGray + $global:ConfigPathFolder = Split-Path $dialog.FileName + $ConfigFileName = $dialog.SafeFileName + $ConfigFileName = $ConfigFileName -replace "\..+" + $global:ConfigPathFull = $dialog.FileName + $global:DataFile = Import-PowerShellDataFile $ConfigPathFull + + Write-Host 'Checking Building Block versions:' -ForegroundColor DarkGray + $dataFileVersion = [System.Version]$DataFile.NonNodeData.BuildingBlock.Version + Write-Host " - Data file version : $($dataFileVersion.ToString())" -ForegroundColor DarkGray + Write-Host " - Script version : $($buildingBlockVersion.ToString())" -ForegroundColor DarkGray + if ($dataFileVersion -eq $buildingBlockVersion) + { + Write-Host 'Versions equal, proceeding...' -ForegroundColor DarkGray + } + else + { + Write-Host 'Versions do not match, please check the building block versions. Quiting!' -ForegroundColor Red + break + } + + # Initialize Credentials variable if it does not exist + if ($null -eq $global:credentials) + { + $global:credentials = @() + } + + # Initialize InstallAccount variable if it does not exist + if ($null -eq $InstallAccount) + { + Write-Host 'Retrieving InstallAccount credentials' -ForegroundColor DarkGray + $global:InstallAccount = Get-Credential -UserName (&whoami) -Message "Please provide your password" + if ($null -eq $global:credentials.UserName -or + -not $global:credentials.UserName.Contains($InstallAccount.UserName)) + { + $global:credentials += $InstallAccount + } + } + + # Initialize PassPhrase variable if it does not exist + if ($null -eq $PassPhrase) + { + Write-Host 'Retrieving Farm Pass Phrase' -ForegroundColor DarkGray + $global:PassPhrase = Get-Credential "Farm Pass Phrase" -Message "Enter the SharePoint Farm PassPhrase" + } + + # Initialize CertPassword variable if it does not exist + if ($null -eq $CertPassword) + { + Write-Host 'Retrieving Certificate password' -ForegroundColor DarkGray + $global:CertPassword = Get-Credential "Certificate Password" -Message "Enter the Certificate Password" + } + + Write-Host 'Retrieving Service Account credentials' -ForegroundColor DarkGray + Get-Accounts -Account $DataFile.NonNodeData.ManagedAccounts.Farm -Description "Farm Account" + Get-Accounts -Account $DataFile.NonNodeData.ManagedAccounts.Services -Description "Generic Services Account" + Get-Accounts -Account $DataFile.NonNodeData.ManagedAccounts.Search -Description "Generic Windows Search Service Account" + Get-Accounts -Account $DataFile.NonNodeData.ManagedAccounts.UpsSync -Description "Generic User Profile Service Account" + Get-Accounts -Account $DataFile.NonNodeData.ManagedAccounts.AppPool -Description "AppPool Account" + Get-Accounts -Account $DataFile.NonNodeData.ServiceAccounts.ContentAccess -Description "Content Access Account" + Get-Accounts -Account $DataFile.NonNodeData.ServiceAccounts.SuperReader -Description "Super Reader Account" + Get-Accounts -Account $DataFile.NonNodeData.ServiceAccounts.SuperUser -Description "Super User Account" + + #Unblock files + Write-Host 'Unblocking setup files in InstallFolder' -ForegroundColor DarkGray + $path = 'C:\SPSources' + if (Test-Path -Path $path) + { + Get-ChildItem -Path $path -Recurse | Unblock-File + } + else + { + Write-Host "Cannot unblock path: $path" -ForegroundColor Red + } + + Write-Host "Completed processing!" -ForegroundColor DarkGray +} +else +{ + Write-Host "Operation Canceled!" -ForegroundColor Red +} diff --git a/SharePointDsc/FarmDeployment/ResetVariables.ps1 b/SharePointDsc/FarmDeployment/ResetVariables.ps1 new file mode 100644 index 000000000..f49bece71 --- /dev/null +++ b/SharePointDsc/FarmDeployment/ResetVariables.ps1 @@ -0,0 +1,18 @@ +#Requires -Version 5.1 + +##### SCRIPT START ##### + +Clear-Host +Write-Host 'Resetting variables for SharePoint deployment' -ForegroundColor Green + +$global:ConfigPathFolder = $null +$global:ConfigPathFull = $null +$global:DataFile = $null + +$global:credentials = $null +$global:InstallAccount = $null + +$global:PassPhrase = $null +$global:CertPassword = $null + +Write-Host "Completed processing!" -ForegroundColor Green diff --git a/SharePointDsc/FarmDeployment/SharePoint 2019.psd1 b/SharePointDsc/FarmDeployment/SharePoint 2019.psd1 new file mode 100644 index 000000000..b4fc47a78 --- /dev/null +++ b/SharePointDsc/FarmDeployment/SharePoint 2019.psd1 @@ -0,0 +1,289 @@ +@{ + AllNodes = @( + @{ + NodeName = "*" + PsDscAllowDomainUser = $true + }, + @{ + NodeName = "SPSrv1" + Thumbprint = "" + CertificateFile = "" + Role = "SharePoint" + Subrole = "SPFE" # SharePoint Role only + IPAddress = @{ + Content = "192.168.0.120" + Apps = "192.168.0.45" + } + }, + @{ + NodeName = "SPSrv2" + Thumbprint = "" + CertificateFile = "" + Role = "SharePoint" + Subrole = "SPFE" # SharePoint Role only + IPAddress = @{ + Content = "192.168.0.121" + Apps = "192.168.0.46" + } + }, + @{ + NodeName = "SPSrv3" + Thumbprint = "" + CertificateFile = "" + Role = "SharePoint" + Subrole = "SPBE", "SearchFE", "SearchBE" # SharePoint Role only + }, + @{ + NodeName = "SPSrv4" + Thumbprint = "" + CertificateFile = "" + Role = "SharePoint" + Subrole = "SPBE", "SearchFE", "SearchBE" # SharePoint Role only + } + ) + NonNodeData = @{ + BuildingBlock = @{ + Version = '1.0.0' + } + InstallPaths = @{ + InstallFolder = '\\server\SPSources\2019' + CertificatesFolder = '\\server\SPCertificates' + } + + Certificates = @{ + Portal = @{ + File = 'portal.contoso.local.pfx' + Thumbprint = '' + FriendlyName = '*.portal.contoso.local' + } + PortalApps = @{ + File = 'portal.contosoapps.local.pfx' + Thumbprint = '' + FriendlyName = '*.portal.contosoapps.local' + } + } + + DomainDetails = @{ + DomainName = 'contoso.local' + NetbiosName = 'contoso' + DBServerCont = 'DBSrv' + DBServerInfr = 'DBSrv' + DBServerSear = 'DBSrv' + DBSAUserName = 'SA' + } + + Logging = @{ + ULSLogPath = 'D:\Logs\ULS' + ULSMaxSizeInGB = 30 # 1-1000 GB, default 1000 + ULSDaysToKeep = 14 # 1-366 days, default 14 + IISLogPath = 'D:\Logs\IIS' + UsageLogPath = 'D:\Logs\Usage' + UsagePerLogInMinutes = 5 # 1-1440 minutes, default 5 + UsageMaxLogSizeInMB = 1 # 1-64 MB, default 1 + } + + SharePoint = @{ + ProductKey = '' + InstallPath = 'C:\Program Files\Microsoft Office Servers\16.0' + DataPath = 'C:\Program Files\Microsoft Office Servers\16.0\Data' + CUFileName = 'sts2019-kb4484472-fullfile-x64-glb.exe' + CULangFileName = 'wssloc2019-kb4484471-fullfile-x64-glb.exe' + ProvisionApps = $true + } + + FarmConfig = @{ + ConfigDBName = 'SP_Config' + AdminContentDBName = 'SP_AdminContent' + SuperReader = 'CONTOSO\svc_cachesr' + SuperUser = 'CONTOSO\svc_cachesu' + OutgoingEmail = @{ + SMTPServer = 'smtp.contoso.local' + From = 'noreply@contoso.local' + ReplyTo = 'noreply@contoso.local' + UseTLS = $true + Port = 25 + } + SearchSettings = @{ + PerformanceLevel = 'Maximum' + ContactEmail = 'sharepointagora@contoso.local' + } + AppsSettings = @{ + AppDomain = 'portal.contosoapps.local' + Prefix = 'app' + AllowAppPurchases = $false + AllowAppsForOffice = $false + } + PasswordChangeSchedule = @{ + Day = "tue" # "mon", "tue", "wed", "thu", "fri", "sat", "sun" + Hour = 7 # Between 00 and 23 + } + } + + CentralAdminSite = @{ + WebAppName = 'SharePoint Central Administration V4' + PhysicalPath = 'C:\inetpub\wwwroot\wss\VirtualDirectories\9334' + AppPool = 'SharePoint Central Administration V4' + SiteURL = 'spca.portal.contoso.local' + Certificate = 'Portal' + } + + ActiveDirectory = @{ + UserOU = 'OU=Service Accounts,DC=contoso,DC=local' + SPAdmins = @{ + Name = 'SPBEHEER' + Description = 'SharePoint Administrators' + Members = @('spadmin', 'svc_setup') + } + } + + ManagedAccounts = @{ + Farm = 'CONTOSO\svc_farm' + Services = 'CONTOSO\svc_service' + Search = 'CONTOSO\svc_service' + UpsSync = 'CONTOSO\svc_adsync' + AppPool = 'CONTOSO\svc_webapp' + } + + ServiceAccounts = @{ + SuperReader = 'CONTOSO\svc_cachesr' + SuperUser = 'CONTOSO\svc_cachesu' + ContentAccess = 'CONTOSO\svc_content' + } + + ApplicationPools = @{ + ServiceApplicationPools = @{ + Name = 'Service Applications' + } + } + + ServiceApplications = @{ + AppManagement = @{ + Name = 'App Management Service Application' + DBName = 'SP_AppManagement' + } + BCSService = @{ + Name = 'BCS Service Application' + DBName = 'SP_BCS' + } + ManagedMetaDataService = @{ + Name = 'Managed Metadata Service Application' + DBName = 'SP_MMS' + TermStoreAdministrators = @('CONTOSO\svc_farm') + } + SearchService = @{ + Name = 'Search Service Application' + DBName = 'SP_Search' + DefaultContentAccessAccount = 'CONTOSO\svc_content' + IndexPartitionRootDirectory = 'D:\SPIndex' + SearchCenterUrl = 'https://search.portal.contoso.local/Pages' + } + SecureStore = @{ + Name = 'Secure Store Service Application' + DBName = 'SP_SecureStore' + } + StateService = @{ + Name = 'State Service Application' + DBName = 'SP_State' + } + SubscriptionSettings = @{ + Name = 'Subscription Settings Service Application' + DBName = 'SP_SubscriptionSettings' + } + UsageAndHealth = @{ + Name = 'Usage and Health Service Application' + DBName = 'SP_UsageAndHealth' + } + UserProfileService = @{ + Name = 'User Profile Service Application' + MySiteHostLocation = 'https://mysite.portal.contoso.local' + ProfileDBName = 'SP_UPA_Profile' + SocialDBName = 'SP_UPA_Social' + SyncDBName = 'SP_UPA_Sync' + UserProfileSyncConnection = @{ + Name = 'Contoso AD' + Forest = 'contoso.local' + UseSSL = $false + Port = 389 + IncludedOUs = @('DC=contoso,DC=local') + ExcludedOUs = @() + Force = $false + ConnectionType = 'ActiveDirectory' + } + } + } + + TrustedIdentityTokenIssuer = @{ + Realm = 'urn:sharepoint:dev' + } + + WebApplications = @{ + Content = @{ + Name = 'SharePoint Content Web Application' + ApplicationPool = 'Content' + ApplicationPoolAccount = 'CONTOSO\svc_webapp' + DatabaseName = 'SP_Content_01' + Url = 'https://root.portal.contoso.local' + Port = '443' + Protocol = 'HTTPS' + Certificate = 'Portal' + CertificateStoreName = 'My' + BlobCacheFolder = 'D:\BlobCache' + BlobCacheSize = 10 # 10-1000 GB, default 10 + BlobCacheFileTypes = '\.(gif|jpg|png|css|js)$' + OwnerAlias = 'CONTOSO\svc_farm' + PathBasedRootSiteCollection = @{ + Url = 'https://root.portal.contoso.local' + Name = 'Root' + Template = 'STS#0' + Language = '1043' + ContentDatabase = 'SP_Content_01' + } + HostNamedSiteCollections = @( + @{ + Url = 'https://appcatalog.portal.contoso.local' + Name = 'AppCatalog' + Template = 'APPCATALOG#0' + Language = '1043' + ContentDatabase = 'SP_Content_01' + }, + @{ + Url = 'https://mysite.portal.contoso.local' + Name = 'MySite' + Template = 'SPSMSITEHOST#0' + Language = '1043' + ContentDatabase = 'SP_Content_01' + }, + @{ + Url = 'https://search.portal.contoso.local' + Name = 'Search Center' + Template = 'SRCHCEN#0' + Language = '1043' + ContentDatabase = 'SP_Content_01' + } + ) + } + Apps = @{ + Name = 'SharePoint Apps Web Application' + ApplicationPool = 'Content' + ApplicationPoolAccount = 'CONTOSO\svc_webapp' + DatabaseName = 'SP_Apps_Content_01' + Url = 'https://root.portal.contosoapps.local' + Port = '443' + Protocol = 'HTTPS' + Certificate = 'PortalApps' + CertificateStoreName = 'My' + BlobCacheFolder = 'D:\BlobCache' + BlobCacheSize = 10 # 10-1000 GB, default 10 + BlobCacheFileTypes = '\.(gif|jpg|png|css|js)$' + OwnerAlias = 'CONTOSO\svc_farm' + PathBasedRootSiteCollection = @{ + Url = 'https://root.portal.contosoapps.local' + Name = 'Root' + Template = 'STS#0' + Language = '1043' + ContentDatabase = 'SP_Apps_Content_01' + } + } + } + } +} diff --git a/SharePointDsc/FarmDeployment/ValidateConfigFile.ps1 b/SharePointDsc/FarmDeployment/ValidateConfigFile.ps1 new file mode 100644 index 000000000..4ecb48993 --- /dev/null +++ b/SharePointDsc/FarmDeployment/ValidateConfigFile.ps1 @@ -0,0 +1,1738 @@ +#Requires -Version 5.1 +[CmdletBinding()] +param () + +#region Supporting functions +function WriteLog +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Message + ) + + $date = Get-Date -f "yyyy-MM-dd hh:mm:ss" + Write-Verbose "$date - $Message" +} + +function WriteError +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Message + ) + + $date = Get-Date -f "yyyy-MM-dd hh:mm:ss" + Write-Output "$date - [ERROR] $Message" + $script:validConfig = $false +} + + +function Confirm-ProductKey +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ProductKey + ) + + $regex = '^([A-Za-z0-9]{5}-){4}[A-Za-z0-9]{5}$' + return ($ProductKey -match $regex) +} + +function Confirm-EmailAddress +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $EmailAddress + ) + + $regex = "^[a-zA-Z0-9.!£#$%&'^_`{}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" + return ($EmailAddress -match $regex) +} + +function Confirm-Path +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + $regex = '^[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$' + return ($Path -match $regex) +} + +function Confirm-CertificateThumbPrint +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ThumbPrint + ) + + $regex = '^[0-9a-f]{40}$' + return ($ThumbPrint -match $regex) +} + +function Confirm-IPAddress +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $IPAddress + ) + + $regex = '\b(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b' + return ($IPAddress -match $regex) +} + +function Confirm-DomainName +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DomainName + ) + + $regex = '^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$' + return ($DomainName -match $regex) +} + +function Confirm-URL +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $URL + ) + + $regex = '(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})' + return ($URL -match $regex) +} + +function Confirm-OUName +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $OUName + ) + + $regex = '^(?(?:(?:OU|CN).+?(?DC.+?))$' + return ($OUName -match $regex) +} + +function Confirm-DomainUserName +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DomainUserName + ) + + $regex = '^[a-zA-Z][a-zA-Z0-9\-\.]{0,61}[a-zA-Z]\\\w[\w\.\- ]+$' + return ($DomainUserName -match $regex) +} +#endregion + +##### GENERIC VARIABLES ##### +$buildingBlockVersion = [System.Version]'1.0.0' +$validConfig = $true +$defaultFolder = $PSScriptRoot + +##### START SCRIPT ##### +WriteLog -Message "Running as $(&whoami)" + +# Dialog for selecting PSD input file +Add-Type -AssemblyName System.Windows.Forms +$dialog = New-Object System.Windows.Forms.OpenFileDialog +$dialog.InitialDirectory = $defaultFolder +$dialog.Title = "Please select the DSC data file" +$dialog.Filter = "DSC Config (*.psd1) | *.psd1" +$result = $dialog.ShowDialog() + +if ($result -eq "OK") +{ + $DataFile = Import-PowerShellDataFile $dialog.FileName +} + +Write-Host 'Checking Building Block versions:' -ForegroundColor DarkGray +$dataFileVersion = [System.Version]$DataFile.NonNodeData.BuildingBlock.Version +Write-Host " - Data file version : $($dataFileVersion.ToString())" -ForegroundColor DarkGray +Write-Host " - Script version : $($buildingBlockVersion.ToString())" -ForegroundColor DarkGray +if ($dataFileVersion -eq $buildingBlockVersion) +{ + Write-Host 'Versions equal, proceeding...' -ForegroundColor DarkGray +} +else +{ + Write-Host 'Versions do not match, please check the building block versions. Quiting!' -ForegroundColor Red + return 'Versions do not match, please check the building block versions.' +} + +#region AllNodes +$spfePresent = $false +$spbePresent = $false +$searchfePresent = $false +$searchbePresent = $false + +WriteLog -Message "Validating Roles and Subroles" +foreach ($node in $DataFile.AllNodes) +{ + WriteLog -Message " Validating $($node.NodeName)" + + # Validating valid roles + if ($node.ContainsKey("Role")) + { + if ($node.Role -notin @("ActiveDirectory", "CAMApps", "SharePoint", "SQLServer")) + { + WriteError -Message "Role of $($node.NodeName) is not valid: $($node.Role)" + } + + # Validating valid subroles + if ($node.ContainsKey("Subrole")) + { + if ($node.Role -eq "SharePoint") + { + if ($node.Subrole -is [System.Array]) + { + foreach ($role in $node.Subrole) + { + # Validating if valid SharePoint subroles are used + if ($role -notin @("SPBE", "SPFE", "SearchBE", "SearchFE")) + { + WriteError -Message "Subrole of $($node.NodeName) is not valid: $role" + } + } + } + else + { + # Validating if valid SharePoint subroles are used + if ($node.Subrole -notin @("SPBE", "SPFE", "SearchBE", "SearchFE")) + { + WriteError -Message "Subrole of $($node.NodeName) is not valid: $($node.Subrole)" + } + } + + # Register if a specific role is specified + switch ($node.Subrole) + { + "SPFE" + { + $spfePresent = $true + if ($node.ContainsKey("IPAddress") -eq $true) + { + if ($node.IPAddress.ContainsKey("Content") -eq $true) + { + if ((Confirm-IPAddress -IPAddress $node.IPAddress.Content) -eq $false) + { + WriteError -Message "Specified Content IPAddress '$($node.IPAddress.Content)' for server $($node.NodeName) is invalid." + } + } + else + { + WriteError -Message "The section IPAddress for server $($node.NodeName) is missing a Content parameter" + } + + if ($node.IPAddress.ContainsKey("Apps") -eq $true) + { + if ((Confirm-IPAddress -IPAddress $node.IPAddress.Apps) -eq $false) + { + WriteError -Message "Specified Apps IPAddress '$($node.IPAddress.Apps)' for server $($node.NodeName) is invalid." + } + } + else + { + WriteError -Message "The section IPAddress for server $($node.NodeName) is missing a Apps parameter" + } + } + else + { + if ($DataFile.NonNodeData.SharePoint.ProvisionApps -eq $true) + { + WriteError -Message "Server $($node.NodeName) with SPFE subrole does not contain a parameter IPAddress" + } + } + } + "SPBE" + { + $spbePresent = $true + if ($node.Subrole -isnot [System.Array] -or $node.Subrole -notcontains "SPFE") + { + if ($node.ContainsKey("IPAddress") -eq $true) + { + WriteError -Message "Server $($node.NodeName) with SPBE subrole should not contain a parameter IPAddress" + } + } + } + "SearchFE" + { + $searchfePresent = $true + if ($node.Subrole -isnot [System.Array] -or $node.Subrole -notcontains "SPFE") + { + if ($node.ContainsKey("IPAddress") -eq $true) + { + WriteError -Message "Server $($node.NodeName) with SearchFE subrole should not contain a parameter IPAddress" + } + } + } + "SearchBE" + { + $searchbePresent = $true + if ($node.Subrole -isnot [System.Array] -or $node.Subrole -notcontains "SPFE") + { + if ($node.ContainsKey("IPAddress") -eq $true) + { + WriteError -Message "Server $($node.NodeName) with SearchBE subrole should not contain a parameter IPAddress" + } + } + } + } + } + else + { + WriteError -Message "$($node.NodeName) has subrole specified, but role is not SharePoint" + } + } + else + { + if ($node.Role -eq "SharePoint") + { + WriteError -Message "$($node.NodeName) has role SharePoint, but doesn't have a subrole specified" + } + } + } + else + { + if ($node.NodeName -ne "*") + { + WriteError -Message "$($node.NodeName) doesn't have a role specified" + } + } + + if ($node.ContainsKey("Thumbprint")) + { + WriteLog -Message " Validating Thumbprint" + if ($node.Thumbprint -ne "" -and + (Confirm-CertificateThumbPrint -ThumbPrint $node.Thumbprint) -eq $false) + { + WriteError -Message "Specified Thumbprint $($node.Thumbprint) is invalid. Make sure it is correct!" + } + } + else + { + if ($node.NodeName -ne "*") + { + WriteError -Message "$($node.NodeName) doesn't have a Thumbprint specified" + } + } + + if ($node.ContainsKey("CertificateFile")) + { + WriteLog -Message " Validating CertificateFile" + if ($node.CertificateFile -ne "" -and + (Test-Path -Path $node.CertificateFile) -eq $false) + { + WriteError -Message "Specified CertificateFile $($node.CertificateFile) does not exist. Make sure that it does!" + } + } + else + { + if ($node.NodeName -ne "*") + { + WriteError -Message "$($node.NodeName) doesn't have a CertificateFile specified" + } + } +} + +WriteLog -Message "Confirm that all SharePoint roles are specified at least once" +if ($spfePresent -eq $false -or $spbePresent -eq $false -or $searchfePresent -eq $false -or $searchbePresent -eq $false) +{ + WriteError -Message "Not all SharePoint roles are specified:" + WriteError -Message "SPFE : $(if ($spfePresent -eq $true) { "Present" } else { "NotPresent" })" + WriteError -Message "SPBE : $(if ($spbePresent -eq $true) { "Present" } else { "NotPresent" })" + WriteError -Message "SearchFE : $(if ($searchfePresent -eq $true) { "Present" } else { "NotPresent" })" + WriteError -Message "SearchBE : $(if ($searchbePresent -eq $true) { "Present" } else { "NotPresent" })" +} +#endregion + +#region InstallPaths +WriteLog -Message "Validating InstallPaths section" +if ($DataFile.NonNodeData.ContainsKey("InstallPaths")) +{ + if ($DataFile.NonNodeData.InstallPaths.ContainsKey("InstallFolder") -eq $false) + { + WriteError -Message "InstallFolder setting is missing in the InstallPaths section" + } + + if ($DataFile.NonNodeData.InstallPaths.ContainsKey("CertificatesFolder") -eq $false) + { + WriteError -Message "CertificatesFolder setting is missing in the InstallPaths section" + } +} +else +{ + WriteError -Message "InstallPaths section is missing in the NonNodeData section" +} +#endregion + +#region Certificates +WriteLog -Message "Validating Certificates section" +if ($DataFile.NonNodeData.ContainsKey("Certificates")) +{ + if ($DataFile.NonNodeData.Certificates.ContainsKey("Portal") -eq $false) + { + WriteError -Message "Portal setting is missing in the Certificates section" + } + else + { + if ($DataFile.NonNodeData.Certificates.Portal.ContainsKey("File") -eq $false) + { + WriteError -Message "File setting is missing in the Certificates\Portal section" + } + + if ($DataFile.NonNodeData.Certificates.Portal.ContainsKey("Thumbprint") -eq $false) + { + WriteError -Message "Thumbprint setting is missing in the Certificates\Portal section" + } + else + { + if ((Confirm-CertificateThumbPrint -ThumbPrint $DataFile.NonNodeData.Certificates.Portal.Thumbprint) -eq $false) + { + WriteError -Message "Specified Thumbprint '$($DataFile.NonNodeData.Certificates.Portal.Thumbprint)' in Certificates\Portal section is invalid." + } + } + + if ($DataFile.NonNodeData.Certificates.Portal.ContainsKey("FriendlyName") -eq $false) + { + WriteError -Message "FriendlyName setting is missing in the Certificates\Portal section" + } + } + + if ($DataFile.NonNodeData.SharePoint.ProvisionApps -eq $true) + { + if ($DataFile.NonNodeData.Certificates.ContainsKey("PortalApps") -eq $false) + { + WriteError -Message "PortalApps setting is missing in the Certificates section" + } + else + { + if ($DataFile.NonNodeData.Certificates.PortalApps.ContainsKey("File") -eq $false) + { + WriteError -Message "File setting is missing in the Certificates\PortalApps section" + } + + if ($DataFile.NonNodeData.Certificates.PortalApps.ContainsKey("Thumbprint") -eq $false) + { + WriteError -Message "Thumbprint setting is missing in the Certificates\PortalApps section" + } + else + { + if ((Confirm-CertificateThumbPrint -ThumbPrint $DataFile.NonNodeData.Certificates.PortalApps.Thumbprint) -eq $false) + { + WriteError -Message "Specified Thumbprint '$($DataFile.NonNodeData.Certificates.PortalApps.Thumbprint)' in Certificates\PortalApps section is invalid." + } + } + + if ($DataFile.NonNodeData.Certificates.PortalApps.ContainsKey("FriendlyName") -eq $false) + { + WriteError -Message "FriendlyName setting is missing in the Certificates\PortalApps section" + } + } + + if ($DataFile.NonNodeData.Certificates.ContainsKey("ProviderApps") -eq $false) + { + WriteError -Message "ProviderApps setting is missing in the Certificates section" + } + else + { + if ($DataFile.NonNodeData.Certificates.ProviderApps.ContainsKey("File") -eq $false) + { + WriteError -Message "File setting is missing in the Certificates\ProviderApps section" + } + + if ($DataFile.NonNodeData.Certificates.ProviderApps.ContainsKey("Thumbprint") -eq $false) + { + WriteError -Message "Thumbprint setting is missing in the Certificates\ProviderApps section" + } + else + { + if ((Confirm-CertificateThumbPrint -ThumbPrint $DataFile.NonNodeData.Certificates.ProviderApps.Thumbprint) -eq $false) + { + WriteError -Message "Specified Thumbprint '$($DataFile.NonNodeData.Certificates.ProviderApps.Thumbprint)' in Certificates\ProviderApps section is invalid." + } + } + + if ($DataFile.NonNodeData.Certificates.ProviderApps.ContainsKey("FriendlyName") -eq $false) + { + WriteError -Message "FriendlyName setting is missing in the Certificates\ProviderApps section" + } + } + } +} +else +{ + WriteError -Message "Certificates section is missing in the NonNodeData section" +} +#endregion + +#region DomainDetails +WriteLog -Message "Validating DomainDetails section" +if ($DataFile.NonNodeData.ContainsKey("DomainDetails")) +{ + if ($DataFile.NonNodeData.DomainDetails.ContainsKey("DomainName") -eq $false) + { + WriteError -Message "DomainName setting is missing in the DomainDetails section" + } + + if ($DataFile.NonNodeData.DomainDetails.ContainsKey("NetbiosName") -eq $false) + { + WriteError -Message "NetbiosName setting is missing in the DomainDetails section" + } + + if ($DataFile.NonNodeData.DomainDetails.ContainsKey("DBServerCont") -eq $false) + { + WriteError -Message "DBServerCont setting is missing in the DomainDetails section" + } + + if ($DataFile.NonNodeData.DomainDetails.ContainsKey("DBServerInfr") -eq $false) + { + WriteError -Message "DBServerInfr setting is missing in the DomainDetails section" + } + + if ($DataFile.NonNodeData.DomainDetails.ContainsKey("DBServerSear") -eq $false) + { + WriteError -Message "DBServerSear setting is missing in the DomainDetails section" + } + + if ($DataFile.NonNodeData.DomainDetails.ContainsKey("DBSAUserName") -eq $false) + { + WriteError -Message "DBSAUserName setting is missing in the DomainDetails section" + } +} +else +{ + WriteError -Message "DomainDetails section is missing in the NonNodeData section" +} +#endregion + +#region Logging +WriteLog -Message "Validating Logging section" +if ($DataFile.NonNodeData.ContainsKey("Logging")) +{ + if ($DataFile.NonNodeData.Logging.ContainsKey("ULSLogPath") -eq $false) + { + WriteError -Message "ULSLogPath setting is missing in the Logging section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.Logging.ULSLogPath) -eq $false) + { + WriteError -Message "ULSLogPath in section Logging is not a valid path" + } + } + + if ($DataFile.NonNodeData.Logging.ContainsKey("ULSMaxSizeInGB") -eq $false) + { + WriteError -Message "ULSMaxSizeInGB setting is missing in the Logging section" + } + else + { + if ($DataFile.NonNodeData.Logging.ULSMaxSizeInGB -lt 1 -or $DataFile.NonNodeData.Logging.ULSMaxSizeInGB -gt 1000) + { + WriteError -Message "ULSMaxSizeInGB setting in the Logging section supports values between 1 and 1000" + } + } + + if ($DataFile.NonNodeData.Logging.ContainsKey("ULSDaysToKeep") -eq $false) + { + WriteError -Message "ULSDaysToKeep setting is missing in the Logging section" + } + else + { + if ($DataFile.NonNodeData.Logging.ULSDaysToKeep -lt 1 -or $DataFile.NonNodeData.Logging.ULSDaysToKeep -gt 366) + { + WriteError -Message "ULSDaysToKeep setting in the Logging section supports values between 1 and 366" + } + } + + if ($DataFile.NonNodeData.Logging.ContainsKey("IISLogPath") -eq $false) + { + WriteError -Message "IISLogPath setting is missing in the Logging section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.Logging.IISLogPath) -eq $false) + { + WriteError -Message "IISLogPath in section Logging is not a valid path" + } + } + + if ($DataFile.NonNodeData.Logging.ContainsKey("UsageLogPath") -eq $false) + { + WriteError -Message "UsageLogPath setting is missing in the Logging section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.Logging.UsageLogPath) -eq $false) + { + WriteError -Message "UsageLogPath in section Logging is not a valid path" + } + } + + if ($DataFile.NonNodeData.Logging.ContainsKey("UsagePerLogInMinutes") -eq $false) + { + WriteError -Message "UsagePerLogInMinutes setting is missing in the Logging section" + } + else + { + if ($DataFile.NonNodeData.Logging.UsagePerLogInMinutes -lt 1 -or $DataFile.NonNodeData.Logging.UsagePerLogInMinutes -gt 1440) + { + WriteError -Message "UsagePerLogInMinutes setting in the Logging section supports values between 1 and 1440" + } + } + + if ($DataFile.NonNodeData.Logging.ContainsKey("UsageMaxLogSizeInMB") -eq $false) + { + WriteError -Message "UsageMaxLogSizeInMB setting is missing in the Logging section" + } + else + { + if ($DataFile.NonNodeData.Logging.UsageMaxLogSizeInMB -lt 1 -or $DataFile.NonNodeData.Logging.UsageMaxLogSizeInMB -gt 64) + { + WriteError -Message "UsageMaxLogSizeInMB setting in the Logging section supports values between 1 and 64" + } + } +} +else +{ + WriteError -Message "Logging section is missing in the NonNodeData section" +} +#endregion + +#region SharePoint +WriteLog -Message "Validating SharePoint section" +if ($DataFile.NonNodeData.ContainsKey("SharePoint")) +{ + if ($DataFile.NonNodeData.SharePoint.ContainsKey("ProductKey") -eq $false) + { + WriteError -Message "ProductKey setting is missing in the SharePoint section" + } + else + { + if ((Confirm-ProductKey -ProductKey $DataFile.NonNodeData.SharePoint.ProductKey) -eq $false) + { + WriteError -Message "Specified ProductKey is not in the correct format" + } + } + + if ($DataFile.NonNodeData.SharePoint.ContainsKey("InstallPath") -eq $false) + { + WriteError -Message "InstallPath setting is missing in the SharePoint section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.SharePoint.InstallPath) -eq $false) + { + WriteError -Message "InstallPath in section SharePoint is not a valid path" + } + } + + if ($DataFile.NonNodeData.SharePoint.ContainsKey("DataPath") -eq $false) + { + WriteError -Message "DataPath setting is missing in the SharePoint section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.SharePoint.DataPath) -eq $false) + { + WriteError -Message "DataPath in section SharePoint is not a valid path" + } + } + + if ($DataFile.NonNodeData.SharePoint.ContainsKey("CUFileName") -eq $false) + { + WriteError -Message "CUFileName setting is missing in the SharePoint section" + } + else + { + if ($DataFile.NonNodeData.SharePoint.CUFileName -isnot [System.String]) + { + WriteError -Message "CUFileName in section SharePoint is not a string" + } + } + + if ($DataFile.NonNodeData.SharePoint.ContainsKey("CULangFileName") -eq $false) + { + WriteError -Message "CULangFileName setting is missing in the SharePoint section" + } + else + { + if ($DataFile.NonNodeData.SharePoint.CULangFileName -isnot [System.String]) + { + WriteError -Message "CULangFileName in section SharePoint is not a string" + } + } + + if ($DataFile.NonNodeData.SharePoint.ContainsKey("ProvisionApps") -eq $false) + { + WriteError -Message "ProvisionApps setting is missing in the SharePoint section" + } + else + { + if ($DataFile.NonNodeData.SharePoint.ProvisionApps -isnot [System.Boolean]) + { + WriteError -Message "ProvisionApps in section SharePoint is not a boolean" + } + } +} +else +{ + WriteError -Message "SharePoint section is missing in the NonNodeData section" +} +#endregion + +#region FarmConfig +WriteLog -Message "Validating FarmConfig section" +if ($DataFile.NonNodeData.ContainsKey("FarmConfig")) +{ + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("ConfigDBName") -eq $false) + { + WriteError -Message "ConfigDBName setting is missing in the FarmConfig section" + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("AdminContentDBName") -eq $false) + { + WriteError -Message "AdminContentDBName setting is missing in the FarmConfig section" + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("SuperReader") -eq $false) + { + WriteError -Message "SuperReader setting is missing in the FarmConfig section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.FarmConfig.SuperReader) -eq $false) + { + WriteError -Message "SuperReader parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("SuperUser") -eq $false) + { + WriteError -Message "SuperUser setting is missing in the FarmConfig section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.FarmConfig.SuperUser) -eq $false) + { + WriteError -Message "SuperUser parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("OutgoingEmail") -eq $false) + { + WriteError -Message "OutgoingEmail setting is missing in the FarmConfig section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail -isnot [System.Collections.Hashtable]) + { + WriteError -Message "OutgoingEmail setting is not a hashtable" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.ContainsKey("SMTPServer") -eq $false) + { + WriteError -Message "SMTPServer setting is missing in the OutgoingEmail section" + } + + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.ContainsKey("From") -eq $false) + { + WriteError -Message "From setting is missing in the OutgoingEmail section" + } + else + { + if ((Confirm-EmailAddress -EmailAddress $DataFile.NonNodeData.FarmConfig.OutgoingEmail.From) -eq $false) + { + WriteError -Message "Specified field From is not a valid e-mail address" + } + } + + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.ContainsKey("ReplyTo") -eq $false) + { + WriteError -Message "ReplyTo setting is missing in the OutgoingEmail section" + } + else + { + if ((Confirm-EmailAddress -EmailAddress $DataFile.NonNodeData.FarmConfig.OutgoingEmail.ReplyTo) -eq $false) + { + WriteError -Message "Specified field ReplyTo is not a valid e-mail address" + } + } + + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.ContainsKey("UseTLS") -eq $false) + { + WriteError -Message "UseTLS setting is missing in the OutgoingEmail section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.UseTLS -isnot [System.Boolean]) + { + WriteError -Message "UseTLS setting is not a boolean (true/false)" + } + } + + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.ContainsKey("Port") -eq $false) + { + WriteError -Message "Port setting is missing in the OutgoingEmail section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.OutgoingEmail.Port -isnot [System.Int32]) + { + WriteError -Message "Port setting is not an integer (number)" + } + } + } + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("SearchSettings") -eq $false) + { + WriteError -Message "SearchSettings setting is missing in the FarmConfig section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.SearchSettings -isnot [System.Collections.Hashtable]) + { + WriteError -Message "SearchSettings setting is not a hashtable" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.SearchSettings.ContainsKey("PerformanceLevel") -eq $false) + { + WriteError -Message "PerformanceLevel setting is missing in the SearchSettings section" + } + + if ($DataFile.NonNodeData.FarmConfig.SearchSettings.ContainsKey("ContactEmail") -eq $false) + { + WriteError -Message "ContactEmail setting is missing in the SearchSettings section" + } + else + { + if ((Confirm-EmailAddress -EmailAddress $DataFile.NonNodeData.FarmConfig.SearchSettings.ContactEmail) -eq $false) + { + WriteError -Message "Specified ContactEmail is not a valid e-mail address" + } + } + } + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("AppsSettings") -eq $false) + { + WriteError -Message "AppsSettings setting is missing in the FarmConfig section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.AppsSettings -isnot [System.Collections.Hashtable]) + { + WriteError -Message "AppsSettings setting is not a hashtable" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.AppsSettings.ContainsKey("AppDomain") -eq $false) + { + WriteError -Message "AppDomain setting is missing in the AppsSettings section" + } + else + { + if ((Confirm-DomainName -DomainName $DataFile.NonNodeData.FarmConfig.AppsSettings.AppDomain) -eq $false) + { + WriteError -Message "AppDomain in section AppsSettings is not a valid domain name" + } + } + + if ($DataFile.NonNodeData.FarmConfig.AppsSettings.ContainsKey("Prefix") -eq $false) + { + WriteError -Message "Prefix setting is missing in the AppsSettings section" + } + + if ($DataFile.NonNodeData.FarmConfig.AppsSettings.ContainsKey("AllowAppPurchases") -eq $false) + { + WriteError -Message "AllowAppPurchases setting is missing in the AppsSettings section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.AppsSettings.AllowAppPurchases -isnot [System.Boolean]) + { + WriteError -Message "AllowAppPurchases setting in section AppsSettings is not a boolean (true/false)" + } + } + + if ($DataFile.NonNodeData.FarmConfig.AppsSettings.ContainsKey("AllowAppsForOffice") -eq $false) + { + WriteError -Message "AllowAppsForOffice setting is missing in the AppsSettings section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.AppsSettings.AllowAppsForOffice -isnot [System.Boolean]) + { + WriteError -Message "AllowAppsForOffice setting in section AppsSettings is not a boolean (true/false)" + } + } + } + } + + if ($DataFile.NonNodeData.FarmConfig.ContainsKey("PasswordChangeSchedule") -eq $false) + { + WriteError -Message "PasswordChangeSchedule setting is missing in the FarmConfig section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.PasswordChangeSchedule -isnot [System.Collections.Hashtable]) + { + WriteError -Message "PasswordChangeSchedule setting is not a hashtable" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.PasswordChangeSchedule.ContainsKey("Day") -eq $false) + { + WriteError -Message "Day setting is missing in the PasswordChangeSchedule section" + } + else + { + if ($DataFile.NonNodeData.FarmConfig.PasswordChangeSchedule.Day -notin @("mon", "tue", "wed", "thu", "fri", "sat", "sun")) + { + WriteError -Message "Day in section PasswordChangeSchedule is not a valid day abbreviation" + } + } + + if ($DataFile.NonNodeData.FarmConfig.PasswordChangeSchedule.ContainsKey("Hour") -eq $false) + { + WriteError -Message "Hour setting is missing in the PasswordChangeSchedule section" + } + else + { + $hour = $DataFile.NonNodeData.FarmConfig.PasswordChangeSchedule.Hour + if ($DataFile.NonNodeData.FarmConfig.PasswordChangeSchedule.Hour -isnot [System.Int32] -or ` + $hour -lt 0 -or ` + $hour -gt 23) + { + WriteError -Message "Hour setting in section PasswordChangeSchedule is not an integer between 0 and 23" + } + } + } + } +} +else +{ + WriteError -Message "FarmConfig section is missing in the NonNodeData section" +} +#endregion + +#region CentralAdminSite +WriteLog -Message "Validating CentralAdminSite section" +if ($DataFile.NonNodeData.ContainsKey("CentralAdminSite")) +{ + if ($DataFile.NonNodeData.CentralAdminSite.ContainsKey("WebAppName") -eq $false) + { + WriteError -Message "WebAppName setting is missing in the CentralAdminSite section" + } + + if ($DataFile.NonNodeData.CentralAdminSite.ContainsKey("PhysicalPath") -eq $false) + { + WriteError -Message "PhysicalPath setting is missing in the CentralAdminSite section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.CentralAdminSite.PhysicalPath) -eq $false) + { + WriteError -Message "PhysicalPath is not a valid path" + } + } + + if ($DataFile.NonNodeData.CentralAdminSite.ContainsKey("AppPool") -eq $false) + { + WriteError -Message "AppPool setting is missing in the CentralAdminSite section" + } + + if ($DataFile.NonNodeData.CentralAdminSite.ContainsKey("SiteURL") -eq $false) + { + WriteError -Message "SiteURL setting is missing in the CentralAdminSite section" + } + else + { + if ((Confirm-DomainName -DomainName $DataFile.NonNodeData.CentralAdminSite.SiteURL) -eq $false) + { + WriteError -Message "SiteURL is not a valid domain name" + } + } + + if ($DataFile.NonNodeData.CentralAdminSite.ContainsKey("Certificate") -eq $false) + { + WriteError -Message "Certificate setting is missing in the CentralAdminSite section" + } +} +else +{ + WriteError -Message "CentralAdminSite section is missing in the NonNodeData section" +} +#endregion + +#region ActiveDirectory +WriteLog -Message "Validating ActiveDirectory section" +if ($DataFile.NonNodeData.ContainsKey("ActiveDirectory")) +{ + if ($DataFile.NonNodeData.ActiveDirectory.ContainsKey("UserOU") -eq $false) + { + WriteError -Message "UserOU setting is missing in the ActiveDirectory section" + } + else + { + if ((Confirm-OUName -OUName $DataFile.NonNodeData.ActiveDirectory.UserOU) -eq $false) + { + WriteError -Message "UserOU is not a valid OU name" + } + } +} +else +{ + WriteError -Message "ActiveDirectory section is missing in the NonNodeData section" +} +#endregion + +#region ManagedAccounts +WriteLog -Message "Validating ManagedAccounts section" +if ($DataFile.NonNodeData.ContainsKey("ManagedAccounts")) +{ + if ($DataFile.NonNodeData.ManagedAccounts.ContainsKey("Farm") -eq $false) + { + WriteError -Message "Farm setting is missing in the ManagedAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ManagedAccounts.Farm) -eq $false) + { + WriteError -Message "Farm parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ManagedAccounts.ContainsKey("Services") -eq $false) + { + WriteError -Message "Services setting is missing in the ManagedAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ManagedAccounts.Services) -eq $false) + { + WriteError -Message "Services parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ManagedAccounts.ContainsKey("Search") -eq $false) + { + WriteError -Message "Search setting is missing in the ManagedAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ManagedAccounts.Search) -eq $false) + { + WriteError -Message "Search parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ManagedAccounts.ContainsKey("UpsSync") -eq $false) + { + WriteError -Message "UpsSync setting is missing in the ManagedAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ManagedAccounts.UpsSync) -eq $false) + { + WriteError -Message "UpsSync parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ManagedAccounts.ContainsKey("AppPool") -eq $false) + { + WriteError -Message "AppPool setting is missing in the ManagedAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ManagedAccounts.AppPool) -eq $false) + { + WriteError -Message "AppPool parameter is not in the valid domain\user format" + } + } +} +else +{ + WriteError -Message "ManagedAccounts section is missing in the NonNodeData section" +} +#endregion + +#region ServiceAccounts +WriteLog -Message "Validating ServiceAccounts section" +if ($DataFile.NonNodeData.ContainsKey("ServiceAccounts")) +{ + if ($DataFile.NonNodeData.ServiceAccounts.ContainsKey("SuperReader") -eq $false) + { + WriteError -Message "SuperReader setting is missing in the ServiceAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ServiceAccounts.SuperReader) -eq $false) + { + WriteError -Message "SuperReader parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ServiceAccounts.ContainsKey("SuperUser") -eq $false) + { + WriteError -Message "SuperUser setting is missing in the ServiceAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ServiceAccounts.SuperUser) -eq $false) + { + WriteError -Message "SuperUser parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ServiceAccounts.ContainsKey("ContentAccess") -eq $false) + { + WriteError -Message "ContentAccess setting is missing in the ServiceAccounts section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ServiceAccounts.ContentAccess) -eq $false) + { + WriteError -Message "ContentAccess parameter is not in the valid domain\user format" + } + } +} +else +{ + WriteError -Message "ServiceAccounts section is missing in the NonNodeData section" +} +#endregion + +#region ApplicationPools +WriteLog -Message "Validating ApplicationPools section" +if ($DataFile.NonNodeData.ContainsKey("ApplicationPools")) +{ + if ($DataFile.NonNodeData.ApplicationPools.ContainsKey("ServiceApplicationPools") -eq $false) + { + WriteError -Message "ServiceApplicationPools setting is missing in the ApplicationPools section" + } + else + { + if ($DataFile.NonNodeData.ApplicationPools.ServiceApplicationPools.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ApplicationPools\ServiceApplicationPools section" + } + } +} +else +{ + WriteError -Message "ApplicationPools section is missing in the NonNodeData section" +} +#endregion + +#region ServiceApplications +WriteLog -Message "Validating ServiceApplications section" +if ($DataFile.NonNodeData.ContainsKey("ServiceApplications")) +{ + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("AppManagement") -eq $false) + { + WriteError -Message "AppManagement setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.AppManagement.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\AppManagement section" + } + + if ($DataFile.NonNodeData.ServiceApplications.AppManagement.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\AppManagement section" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("BCSService") -eq $false) + { + WriteError -Message "BCSService setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.BCSService.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\BCSService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.BCSService.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\BCSService section" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("ManagedMetaDataService") -eq $false) + { + WriteError -Message "ManagedMetaDataService setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.ManagedMetaDataService.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\ManagedMetaDataService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.ManagedMetaDataService.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\ManagedMetaDataService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.ManagedMetaDataService.ContainsKey("TermStoreAdministrators") -eq $false) + { + WriteError -Message "TermStoreAdministrators setting is missing in the ServiceApplications\ManagedMetaDataService section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.ManagedMetaDataService.TermStoreAdministrators -isnot [System.Array]) + { + WriteError -Message "TermStoreAdministrators setting is not an array" + } + else + { + foreach ($admin in $DataFile.NonNodeData.ServiceApplications.ManagedMetaDataService.TermStoreAdministrators) + { + if ((Confirm-DomainUserName -DomainUserName $admin) -eq $false) + { + WriteError -Message "Specified user $admin in TermStoreAdministrators parameter is not in the valid domain\user format" + } + } + } + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("SearchService") -eq $false) + { + WriteError -Message "SearchService setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.SearchService.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\SearchService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.SearchService.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\SearchService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.SearchService.ContainsKey("DefaultContentAccessAccount") -eq $false) + { + WriteError -Message "DefaultContentAccessAccount setting is missing in the ServiceApplications\SearchService section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $DataFile.NonNodeData.ServiceApplications.SearchService.DefaultContentAccessAccount) -eq $false) + { + WriteError -Message "DefaultContentAccessAccount parameter is not in the valid domain\user format" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.SearchService.ContainsKey("IndexPartitionRootDirectory") -eq $false) + { + WriteError -Message "IndexPartitionRootDirectory setting is missing in the ServiceApplications\SearchService section" + } + else + { + if ((Confirm-Path -Path $DataFile.NonNodeData.ServiceApplications.SearchService.IndexPartitionRootDirectory) -eq $false) + { + WriteError -Message "IndexPartitionRootDirectory parameter in ServiceApplications\SearchService section is not a valid path" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.SearchService.ContainsKey("SearchCenterUrl") -eq $false) + { + WriteError -Message "SearchCenterUrl setting is missing in the ServiceApplications\SearchService section" + } + else + { + if ((Confirm-Url -URL $DataFile.NonNodeData.ServiceApplications.SearchService.SearchCenterUrl) -eq $false) + { + WriteError -Message "SearchCenterUrl parameter in ServiceApplications\SearchService section is not a valid URL" + } + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("SecureStore") -eq $false) + { + WriteError -Message "SecureStore setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.SecureStore.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\SecureStore section" + } + + if ($DataFile.NonNodeData.ServiceApplications.SecureStore.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\SecureStore section" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("StateService") -eq $false) + { + WriteError -Message "StateService setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.StateService.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\StateService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.StateService.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\StateService section" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("SubscriptionSettings") -eq $false) + { + WriteError -Message "SubscriptionSettings setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.SubscriptionSettings.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\SubscriptionSettings section" + } + + if ($DataFile.NonNodeData.ServiceApplications.SubscriptionSettings.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\SubscriptionSettings section" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("UsageAndHealth") -eq $false) + { + WriteError -Message "UsageAndHealth setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UsageAndHealth.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\UsageAndHealth section" + } + + if ($DataFile.NonNodeData.ServiceApplications.UsageAndHealth.ContainsKey("DBName") -eq $false) + { + WriteError -Message "DBName setting is missing in the ServiceApplications\UsageAndHealth section" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.ContainsKey("UserProfileService") -eq $false) + { + WriteError -Message "UserProfileService setting is missing in the ServiceApplications section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\UserProfileService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.ContainsKey("MySiteHostLocation") -eq $false) + { + WriteError -Message "MySiteHostLocation setting is missing in the ServiceApplications\UserProfileService section" + } + else + { + if ((Confirm-Url -URL $DataFile.NonNodeData.ServiceApplications.UserProfileService.MySiteHostLocation) -eq $false) + { + WriteError -Message "MySiteHostLocation parameter in ServiceApplications\UserProfileService section is not a valid URL" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.ContainsKey("ProfileDBName") -eq $false) + { + WriteError -Message "ProfileDBName setting is missing in the ServiceApplications\UserProfileService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.ContainsKey("SocialDBName") -eq $false) + { + WriteError -Message "SocialDBName setting is missing in the ServiceApplications\UserProfileService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.ContainsKey("SyncDBName") -eq $false) + { + WriteError -Message "SyncDBName setting is missing in the ServiceApplications\UserProfileService section" + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.ContainsKey("UserProfileSyncConnection") -eq $false) + { + WriteError -Message "UserProfileSyncConnection setting is missing in the ServiceApplications\UserProfileService section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the ServiceApplications\UserProfileService\UserProfileSyncConnection section" + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ContainsKey("Forest") -eq $false) + { + WriteError -Message "Forest setting is missing in the ServiceApplications\UserProfileService\UserProfileSyncConnection section" + } + else + { + if ((Confirm-DomainName -DomainName $DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Forest) -eq $false) + { + WriteError -Message "Forest parameter in section UserProfileSyncConnection is not a valid domain name" + } + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ContainsKey("UseSSL") -eq $false) + { + WriteError -Message "UseSSL setting is missing in the ServiceApplications\UserProfileService\UserProfileSyncConnection section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.UseSSL -isnot [System.Boolean]) + { + WriteError -Message 'UseSSL setting in section UserProfileSyncConnection must be $true or $false' + } + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ContainsKey("Port") -eq $false) + { + WriteError -Message "Port setting is missing in the ServiceApplications\UserProfileService\UserProfileSyncConnection section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Port -isnot [System.Int32]) + { + WriteError -Message "Port setting in section UserProfileSyncConnection is not a number" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Port -lt 1 -or + $DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.Port -gt 65534) + { + WriteError -Message "Port setting in section UserProfileSyncConnection must be between 0 and 65535" + } + } + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ContainsKey("IncludedOUs") -eq $false) + { + WriteError -Message "IncludedOUs setting is missing in the ServiceApplications\UserProfileService\UserProfileSyncConnection section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.IncludedOUs -isnot [System.Array]) + { + WriteError -Message "IncludedOUs setting in section UserProfileSyncConnection is not an array" + } + else + { + foreach ($ou in $DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.IncludedOUs) + { + if ((Confirm-OUName -OUName $ou) -eq $false) + { + WriteError -Message "IncludedOUs in section UserProfileSyncConnection contains invalid OU's" + } + } + } + } + + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ContainsKey("ExcludedOUs") -eq $false) + { + WriteError -Message "ExcludedOUs setting is missing in the ServiceApplications\UserProfileService\UserProfileSyncConnection section" + } + else + { + if ($DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ExcludedOUs -isnot [System.Array]) + { + WriteError -Message "ExcludedOUs setting in section UserProfileSyncConnection is not an array" + } + else + { + foreach ($ou in $DataFile.NonNodeData.ServiceApplications.UserProfileService.UserProfileSyncConnection.ExcludedOUs) + { + if ((Confirm-OUName -OUName $ou) -eq $false) + { + WriteError -Message "ExcludedOUs in section UserProfileSyncConnection contains invalid OU's" + } + } + } + } + } + } +} +else +{ + WriteError -Message "ServiceApplications section is missing in the NonNodeData section" +} +#endregion + +#region TrustedIdentityTokenIssuer +WriteLog -Message "Validating TrustedIdentityTokenIssuer section" +if ($DataFile.NonNodeData.ContainsKey("TrustedIdentityTokenIssuer")) +{ + if ($DataFile.NonNodeData.TrustedIdentityTokenIssuer.ContainsKey("Realm") -eq $false) + { + WriteError -Message "Realm setting is missing in the TrustedIdentityTokenIssuer section" + } + else + { + } +} +#endregion + +#region WebApplications +WriteLog -Message "Validating WebApplications section" +if ($DataFile.NonNodeData.ContainsKey("WebApplications")) +{ + foreach ($webapp in $DataFile.NonNodeData.WebApplications.GetEnumerator()) + { + if ($DataFile.NonNodeData.SharePoint.ProvisionApps -eq $false -and + $webAppEnum.Key -eq 'Apps') + { + continue + } + + $webappData = $webapp.Value + if ($webappData.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in the WebApplications $($webapp.Name) section" + } + + if ($webappData.ContainsKey("ApplicationPool") -eq $false) + { + WriteError -Message "ApplicationPool setting is missing in WebApplication $($webapp.Name) section" + } + + if ($webappData.ContainsKey("ApplicationPoolAccount") -eq $false) + { + WriteError -Message "ApplicationPoolAccount setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $webappData.ApplicationPoolAccount) -eq $false) + { + WriteError -Message "Specified ApplicationPoolAccount $($webappData.ApplicationPoolAccount) in WebApplication $($webapp.Name) section is not in the valid domain\user format" + } + + } + + if ($webappData.ContainsKey("DatabaseName") -eq $false) + { + WriteError -Message "DatabaseName setting is missing in WebApplication $($webapp.Name) section" + } + + if ($webappData.ContainsKey("URL") -eq $false) + { + WriteError -Message "URL setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ((Confirm-URL -URL $webappData.URL) -eq $false) + { + WriteError -Message "Specified URL $($webappData.URL) in WebApplication $($webapp.Name) section is not a valid URL" + } + } + + if ($webappData.ContainsKey("Port") -eq $false) + { + WriteError -Message "Port setting is missing in WebApplication $($webapp.Name) section" + } + + if ($webappData.ContainsKey("Protocol") -eq $false) + { + WriteError -Message "Protocol setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ($webappData.Protocol -notin @("HTTP", "HTTPS")) + { + WriteError -Message "Protocol '$($webappData.Protocol)' in WebApplication $($webapp.Name) section is invalid. It can only be HTTP or HTTPS" + } + } + + if ($webappData.ContainsKey("Certificate") -eq $false) + { + WriteError -Message "Certificate setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ($webappData.Certificate -isnot [System.String]) + { + WriteError -Message "Certificate in WebApplication $($webapp.Name) section is not a string." + } + } + + if ($webappData.ContainsKey("CertificateStoreName") -eq $false) + { + WriteError -Message "CertificateStoreName setting is missing in WebApplication $($webapp.Name) section" + } + + if ($webappData.ContainsKey("BlobCacheFolder") -eq $false) + { + WriteError -Message "BlobCacheFolder setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ((Confirm-Path -Path $webappData.BlobCacheFolder) -eq $false) + { + WriteError -Message "Specified BlobCacheFolder $($webappData.BlobCacheFolder) in WebApplication $($webapp.Name) section is not a valid path" + } + } + + if ($webappData.ContainsKey("BlobCacheSize") -eq $false) + { + WriteError -Message "BlobCacheSize setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ($webappData.BlobCacheSize -isnot [System.Int32]) + { + WriteError -Message "Specified BlobCacheSize $($webappData.BlobCacheSize) in WebApplication $($webapp.Name) section is not a valid number" + } + } + + if ($webappData.ContainsKey("BlobCacheFileTypes") -eq $false) + { + WriteError -Message "BlobCacheFileTypes setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ($webappData.BlobCacheFileTypes -isnot [System.String]) + { + WriteError -Message "Specified BlobCacheFileTypes $($webappData.BlobCacheFileTypes) in WebApplication $($webapp.Name) section is not a valid string" + } + } + + if ($webappData.ContainsKey("OwnerAlias") -eq $false) + { + WriteError -Message "OwnerAlias setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ((Confirm-DomainUserName -DomainUserName $webappData.OwnerAlias) -eq $false) + { + WriteError -Message "Specified ApplicationPoolAccount $($webappData.OwnerAlias) in WebApplication $($webapp.Name) section is not in the valid domain\user format" + } + } + + if ($webappData.ContainsKey("PathBasedRootSiteCollection") -eq $false) + { + WriteError -Message "PathBasedRootSiteCollection setting is missing in WebApplication $($webapp.Name) section" + } + else + { + if ($webappData.PathBasedRootSiteCollection.ContainsKey("URL") -eq $false) + { + WriteError -Message "URL setting is missing in PathBasedRootSiteCollection section of WebApplication $($webapp.Name)" + } + else + { + if ((Confirm-URL -URL $webappData.PathBasedRootSiteCollection.URL) -eq $false) + { + WriteError -Message "Specified URL $($webappData.URL) in PathBasedRootSiteCollection section of web application $($webapp.Name) is not a valid URL" + } + } + + if ($webappData.PathBasedRootSiteCollection.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in PathBasedRootSiteCollection section of WebApplication $($webapp.Name)" + } + + if ($webappData.PathBasedRootSiteCollection.ContainsKey("Template") -eq $false) + { + WriteError -Message "Template setting is missing in PathBasedRootSiteCollection section of WebApplication $($webapp.Name)" + } + + if ($webappData.PathBasedRootSiteCollection.ContainsKey("Language") -eq $false) + { + WriteError -Message "Language setting is missing in PathBasedRootSiteCollection section of WebApplication $($webapp.Name)" + } + + if ($webappData.PathBasedRootSiteCollection.ContainsKey("ContentDatabase") -eq $false) + { + WriteError -Message "ContentDatabase setting is missing in PathBasedRootSiteCollection section of WebApplication $($webapp.Name)" + } + } + + if (($webappData.ContainsKey("HostNamedSiteCollections")) -eq $true) + { + foreach ($hnsc in $webappData.HostNamedSiteCollections) + { + if ($hnsc.ContainsKey("URL") -eq $false) + { + WriteError -Message "URL setting is missing in HostNamedSiteCollections section of WebApplication $($webapp.Name)" + } + else + { + if ((Confirm-URL -URL $hnsc.URL) -eq $false) + { + WriteError -Message "Specified URL $($webappData.URL) in HostNamedSiteCollections section of web application $($webapp.Name) is not a valid URL" + } + } + + if ($hnsc.ContainsKey("Name") -eq $false) + { + WriteError -Message "Name setting is missing in HostNamedSiteCollections section of WebApplication $($webapp.Name)" + } + + if ($hnsc.ContainsKey("Template") -eq $false) + { + WriteError -Message "Template setting is missing in HostNamedSiteCollections section of WebApplication $($webapp.Name)" + } + + if ($hnsc.ContainsKey("Language") -eq $false) + { + WriteError -Message "Language setting is missing in HostNamedSiteCollections section of WebApplication $($webapp.Name)" + } + + if ($hnsc.ContainsKey("ContentDatabase") -eq $false) + { + WriteError -Message "ContentDatabase setting is missing in HostNamedSiteCollections section of WebApplication $($webapp.Name)" + } + } + } + } +} +else +{ + WriteError -Message "WebApplications section is missing in the NonNodeData section" +} +#endregion + +if ($validConfig -eq $false) +{ + WriteError -Message " " + WriteError -Message "Validation failed! Configuration contains errors." +} + +return $validConfig diff --git a/SharePointDsc/WikiSource/Creating-Configuration-Files.md b/SharePointDsc/WikiSource/Creating-Configuration-Files.md index 50545474e..028bbed94 100644 --- a/SharePointDsc/WikiSource/Creating-Configuration-Files.md +++ b/SharePointDsc/WikiSource/Creating-Configuration-Files.md @@ -94,10 +94,15 @@ This means that the farm server configuration will likely be much larger than an configuration, but it also provides more flexibility in the implementation as all logical components are in one spot on one server. +As an example, checkout the FarmDeployment folder in the repository. This folder contains a DSC configuration and several accompanying scripts that combined can be used to prepare and configure a SharePoint farm, in various setups. The configuration also configure other settings related to SharePoint, like disabling SSLv3. See [Instructions](Instructions) for more information. + ## Examples -In the module is an examples directory which demonstrates both a single server deployment, as well as the -three configurations needed for the above mentioned multi-server example (so one "farm" configuration, -then an app server and a front end server). +The module also contains an Examples directory in which several examples are available: + +- Examples for each of the SharePointDsc resources +- A single server deployment, where all SharePoint components are deployed to one server +- A small farm deployment, with an application and a front end server + These examples demonstrate the concepts discussed here and can be used as as starting point for your own configurations. diff --git a/SharePointDsc/WikiSource/Instructions.md b/SharePointDsc/WikiSource/Instructions.md new file mode 100644 index 000000000..1632abbbb --- /dev/null +++ b/SharePointDsc/WikiSource/Instructions.md @@ -0,0 +1,36 @@ +# Usage instructions for the "Flexible Farm" example + +We have created an example configuration that can be used to deploy SharePoint environments in a flexible way. + +The configuration is able to deploy SharePoint using Front-End, Back-End, Search Front-End and Search Back-End servers. All on one server or spread across multiple servers. + +**NOTE:** This script hasn't been fully tested yet. We are in the process of doing that, but wanted to share it anyways so it can be used as an example by others. + +## Scripts + +Consists our of seven files: + +| File name | Description | +| --- | ---| +| **SharePoint 2019.psd1** | PowerShell data file used as Configuration Data file for the DSC Configuration| +| **ValidateConfigFile.ps1** | Script to validate if the data file has the correct fields and data | +| **PrepVariables.ps1** | Script that loads the data file into memory, requests additional data (like passwords), so it can be used by all other files. | +| **PrepServers.ps1** | Script that prepares the target servers: Deploy DSC modules, generate DSC encryption certificate and configure LCM | +| **PrepServersConfig.ps1** | Meta DSC Configuration used to configure the LCM | +| **Deploy_SharePoint.ps1** | DSC Configuration to deploy and configure SharePoint | +| **ResetVariables.ps1** | Script to reset all password variables, so the PrepVariables script will ask for them again (for example: if you have entered an incorrect password). | + +## Usage + +1. Make sure you update the values in the **SharePoint 2019.psd1** +1. Run the **ValidateConfigFile.ps1** to validate that the psd1 is valid +1. Run the **PrepVariables.ps1** script, select the file **SharePoint 2019.psd1** and enter the credentials of the used accounts +1. Run **PrepServers.ps1** to configure the target servers correctly +1. Run **Deploy_SharePoint.ps1** to compile the MOF files +1. Run `Start-DscConfiguration` to deploy the MOF files + +**NOTE:** If you have made a mistake entering credentials, just run the **ResetVariables.ps1** script and run the **PrepVariables.ps1** script again. + +## Location + +The example can be found [here](https://github.com/dsccommunity/SharePointDsc/tree/master/SharePointDsc/Examples/Flexible%20Farm). diff --git a/azure-pipelines.yml b/azure-pipelines.yml index afc0c992e..908780c24 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,14 +23,17 @@ stages: - job: Package_Module displayName: 'Package Module' pool: - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-latest' steps: - - task: GitVersion@5 - name: gitVersion - displayName: 'Evaluate Next Version' - inputs: - runtime: 'core' - configFilePath: 'GitVersion.yml' + - pwsh: | + dotnet tool install --global GitVersion.Tool + $gitVersionObject = dotnet-gitversion | ConvertFrom-Json + $gitVersionObject.PSObject.Properties.ForEach{ + Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." + Write-Host -Object "##vso[task.setvariable variable=$($_.Name);]$($_.Value)" + } + Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)" + displayName: Calculate ModuleVersion (GitVersion) - task: PowerShell@2 name: package @@ -40,7 +43,7 @@ stages: arguments: '-ResolveDependency -tasks pack' pwsh: true env: - ModuleVersion: $(gitVersion.NuGetVersionV2) + ModuleVersion: $(NuGetVersionV2) - task: PublishPipelineArtifact@1 displayName: 'Publish Pipeline Artifact' diff --git a/tests/Unit/SharePointDsc/SharePointDsc.SPSearchIndexPartition.Tests.ps1 b/tests/Unit/SharePointDsc/SharePointDsc.SPSearchIndexPartition.Tests.ps1 index 4ae862a8f..5f68fe0b8 100644 --- a/tests/Unit/SharePointDsc/SharePointDsc.SPSearchIndexPartition.Tests.ps1 +++ b/tests/Unit/SharePointDsc/SharePointDsc.SPSearchIndexPartition.Tests.ps1 @@ -260,7 +260,7 @@ try Index = 1; PsDscRunAsCredential = \$Credsspfarm; RootDirectory = "I:\\SearchIndexes\\1"; - Servers = "\$ConfigurationData.NonNodeData.SearchIndexPartitionServers"; + Servers = \$ConfigurationData.NonNodeData.SearchIndexPartitionServers; ServiceAppName = "Search Service Application"; } diff --git a/tests/Unit/SharePointDsc/SharePointDsc.SPSearchTopology.Tests.ps1 b/tests/Unit/SharePointDsc/SharePointDsc.SPSearchTopology.Tests.ps1 index b282157c7..b275639c1 100644 --- a/tests/Unit/SharePointDsc/SharePointDsc.SPSearchTopology.Tests.ps1 +++ b/tests/Unit/SharePointDsc/SharePointDsc.SPSearchTopology.Tests.ps1 @@ -621,14 +621,14 @@ try $result = @' SPSearchTopology [0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12} { - Admin = "\$ConfigurationData.NonNodeData.SearchAdminServers"; - AnalyticsProcessing = "\$ConfigurationData.NonNodeData.SearchAnalyticsProcessingServers"; - ContentProcessing = "\$ConfigurationData.NonNodeData.SearchContentProcessingServers"; - Crawler = "\$ConfigurationData.NonNodeData.SearchCrawlerServers"; + Admin = \$ConfigurationData.NonNodeData.SearchAdminServers; + AnalyticsProcessing = \$ConfigurationData.NonNodeData.SearchAnalyticsProcessingServers; + ContentProcessing = \$ConfigurationData.NonNodeData.SearchContentProcessingServers; + Crawler = \$ConfigurationData.NonNodeData.SearchCrawlerServers; FirstPartitionDirectory = "I:\\SearchIndexes\\0"; - IndexPartition = "\$ConfigurationData.NonNodeData.SearchIndexPartitionServers"; + IndexPartition = \$ConfigurationData.NonNodeData.SearchIndexPartitionServers; PsDscRunAsCredential = \$Credsspfarm; - QueryProcessing = "\$ConfigurationData.NonNodeData.QueryProcessingServers"; + QueryProcessing = \$ConfigurationData.NonNodeData.QueryProcessingServers; ServiceAppName = "Search Service Application"; }