DSCResources/MSFT_SPFarm/MSFT_SPFarm.psm1
$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' $script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'SharePointDsc.Util' Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'SharePointDsc.Util.psm1') $script:resourceFarmHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'SharePointDsc.Farm' Import-Module -Name (Join-Path -Path $script:resourceFarmHelperModulePath -ChildPath 'SPFarm.psm1') function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateSet('Yes')] [String] $IsSingleInstance, [Parameter()] [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [Parameter(Mandatory = $true)] [System.String] $FarmConfigDatabaseName, [Parameter(Mandatory = $true)] [System.String] $DatabaseServer, [Parameter()] [System.Boolean] $UseSQLAuthentication, [Parameter()] [System.Management.Automation.PSCredential] $DatabaseCredentials, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $FarmAccount, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Passphrase, [Parameter(Mandatory = $true)] [System.String] $AdminContentDatabaseName, [Parameter(Mandatory = $true)] [System.Boolean] $RunCentralAdmin, [Parameter()] [System.String] $CentralAdministrationUrl, [Parameter()] [ValidateRange(1, 65535)] [System.UInt32] $CentralAdministrationPort, [Parameter()] [System.String] [ValidateSet("NTLM", "Kerberos")] $CentralAdministrationAuth, [Parameter()] [System.String] [ValidateSet("Application", "ApplicationWithSearch", "Custom", "DistributedCache", "Search", "SingleServerFarm", "WebFrontEnd", "WebFrontEndWithDistributedCache")] $ServerRole, [Parameter()] [ValidateSet("Off", "On", "OnDemand")] [System.String] $DeveloperDashboard, [Parameter()] [System.Management.Automation.PSCredential] $ApplicationCredentialKey, [Parameter()] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Getting the settings of the current local SharePoint Farm (if any)" if ($Ensure -eq "Absent") { throw "SharePointDsc does not support removing a server from a farm, please set the ensure property to 'present'" } $supportsSettingApplicationCredentialKey = $false $installedVersion = Get-SPDscInstalledProductVersion switch ($installedVersion.FileMajorPart) { 15 { Write-Verbose -Message "Detected installation of SharePoint 2013" } 16 { if ($DeveloperDashboard -eq "OnDemand") { throw "The DeveloperDashboard value 'OnDemand' is not allowed in SharePoint 2016 and 2019" } if ($DeveloperDashboard -eq "On") { $message = "Please make sure you also provision the Usage and Health " + "service application to make sure the Developer Dashboard " + "works properly" Write-Verbose -Message $message } if ($installedVersion.ProductBuildPart.ToString().Length -eq 4) { Write-Verbose -Message "Detected installation of SharePoint 2016" } else { Write-Verbose -Message "Detected installation of SharePoint 2019" $supportsSettingApplicationCredentialKey = $true } } default { throw ("Detected an unsupported major version of SharePoint. SharePointDsc only " + "supports SharePoint 2013, 2016 or 2019.") } } if ($PSBoundParameters.ContainsKey("ApplicationCredentialKey") -and -not $supportsSettingApplicationCredentialKey) { throw [Exception] ("Specifying ApplicationCredentialKey is only supported " + "on SharePoint 2019") } if (($PSBoundParameters.ContainsKey("ServerRole") -eq $true) -and $installedVersion.FileMajorPart -ne 16) { throw [Exception] "Server role is only supported in SharePoint 2016 and 2019." } if (($PSBoundParameters.ContainsKey("ServerRole") -eq $true) -and $installedVersion.FileMajorPart -eq 16 -and $installedVersion.FileBuildPart -lt 4456 -and ($ServerRole -eq "ApplicationWithSearch" -or $ServerRole -eq "WebFrontEndWithDistributedCache")) { throw [Exception] ("ServerRole values of 'ApplicationWithSearch' or " + "'WebFrontEndWithDistributedCache' require the SharePoint 2016 " + "Feature Pack 1 to be installed. See " + "https://support.microsoft.com/en-us/kb/3127940") } # Determine if a connection to a farm already exists $majorVersion = $installedVersion.FileMajorPart $regPath = "hklm:SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\$majorVersion.0\Secure\ConfigDB" $dsnValue = Get-SPDscRegistryKey -Key $regPath -Value "dsn" -ErrorAction SilentlyContinue if ($null -ne $dsnValue) { Write-Verbose -Message "This node has already been connected to a farm" $result = Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -ScriptBlock { $params = $args[0] try { $spFarm = Get-SPFarm } catch { Write-Verbose -Message "Unable to detect local farm." return $null } if ($null -eq $spFarm) { return $null } $configDb = Get-SPDatabase | Where-Object -FilterScript { $_.Name -eq $spFarm.Name -and $_.Type -eq "Configuration Database" } if ($params.FarmAccount.UserName -eq $spFarm.DefaultServiceAccount.Name) { $farmAccount = $params.FarmAccount } else { $farmAccount = $spFarm.DefaultServiceAccount.Name } $centralAdminSite = Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication -eq $true } $centralAdminProvisioned = $false $ca = Get-SPServiceInstance -Server $env:ComputerName if ($null -ne $ca) { $ca = $ca | Where-Object -Filterscript { $_.GetType().Name -eq "SPWebServiceInstance" -and $_.Name -eq "WSS_Administration" -and $_.Status -eq "Online" } } if ($null -ne $ca) { $centralAdminProvisioned = $true } $centralAdminAuth = $null if ($null -ne $centralAdminSite -and $centralAdminSite.IisSettings[0].DisableKerberos -eq $false) { $centralAdminAuth = "Kerberos" } else { $centralAdminAuth = "NTLM" } $admService = Get-SPDscContentService $developerDashboardSettings = $admService.DeveloperDashboardSettings $developerDashboardStatus = $developerDashboardSettings.DisplayLevel $returnValue = @{ IsSingleInstance = "Yes" FarmConfigDatabaseName = $spFarm.Name DatabaseServer = $configDb.NormalizedDataSource FarmAccount = $farmAccount # Need to return this as a credential to match the type expected Passphrase = $null AdminContentDatabaseName = $centralAdminSite.ContentDatabases[0].Name RunCentralAdmin = $centralAdminProvisioned CentralAdministrationUrl = $centralAdminSite.Url.TrimEnd('/') CentralAdministrationPort = (New-Object -TypeName System.Uri $centralAdminSite.Url).Port CentralAdministrationAuth = $centralAdminAuth DeveloperDashboard = $developerDashboardStatus ApplicationCredentialKey = $null } $installedVersion = Get-SPDscInstalledProductVersion if ($installedVersion.FileMajorPart -eq 16) { $server = Get-SPServer -Identity $env:COMPUTERNAME -ErrorAction SilentlyContinue if ($null -ne $server -and $null -ne $server.Role) { $returnValue.Add("ServerRole", $server.Role) } else { $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain $currentServer = "$($env:COMPUTERNAME).$domain" $server = Get-SPServer -Identity $currentServer -ErrorAction SilentlyContinue if ($null -ne $server -and $null -ne $server.Role) { $returnValue.Add("ServerRole", $server.Role) } } } return $returnValue } if ($null -eq $result) { # The node is currently connected to a farm but was unable to retrieve the values # of current farm settings, most likely due to connectivity issues with the SQL box Write-Verbose -Message ("This server appears to be connected to a farm already, " + "but the configuration database is currently unable to be " + "accessed. Values returned from the get method will be " + "incomplete, however the 'Ensure' property should be " + "considered correct") return @{ IsSingleInstance = "Yes" FarmConfigDatabaseName = $null DatabaseServer = $null FarmAccount = $null Passphrase = $null AdminContentDatabaseName = $null RunCentralAdmin = $null CentralAdministrationUrl = $null CentralAdministrationPort = $null CentralAdministrationAuth = $null ApplicationCredentialKey = $null Ensure = "Present" } } else { $result.Add("Ensure", "Present") return $result } } else { Write-Verbose -Message "This node has never been connected to a farm" # Return the null return object return @{ IsSingleInstance = "Yes" FarmConfigDatabaseName = $null DatabaseServer = $null FarmAccount = $null Passphrase = $null AdminContentDatabaseName = $null RunCentralAdmin = $null CentralAdministrationUrl = $null CentralAdministrationPort = $null CentralAdministrationAuth = $null ApplicationCredentialKey = $null Ensure = "Absent" } } } function Set-TargetResource { # Supressing the global variable use to allow passing DSC the reboot message [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] param ( [Parameter(Mandatory = $true)] [ValidateSet('Yes')] [String] $IsSingleInstance, [Parameter()] [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [Parameter(Mandatory = $true)] [System.String] $FarmConfigDatabaseName, [Parameter(Mandatory = $true)] [System.String] $DatabaseServer, [Parameter()] [System.Boolean] $useSQLAuthentication, [Parameter()] [System.Management.Automation.PSCredential] $DatabaseCredentials, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $FarmAccount, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Passphrase, [Parameter(Mandatory = $true)] [System.String] $AdminContentDatabaseName, [Parameter(Mandatory = $true)] [System.Boolean] $RunCentralAdmin, [Parameter()] [System.String] $CentralAdministrationUrl, [Parameter()] [ValidateRange(1, 65535)] [System.UInt32] $CentralAdministrationPort, [Parameter()] [System.String] [ValidateSet("NTLM", "Kerberos")] $CentralAdministrationAuth, [Parameter()] [System.String] [ValidateSet("Application", "ApplicationWithSearch", "Custom", "DistributedCache", "Search", "SingleServerFarm", "WebFrontEnd", "WebFrontEndWithDistributedCache")] $ServerRole, [Parameter()] [ValidateSet("Off", "On", "OnDemand")] [System.String] $DeveloperDashboard, [Parameter()] [System.Management.Automation.PSCredential] $ApplicationCredentialKey, [Parameter()] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Setting local SP Farm settings" if ($Ensure -eq "Absent") { throw ("SharePointDsc does not support removing a server from a farm, please set the " + "ensure property to 'present'") } if ($PSBoundParameters.ContainsKey("CentralAdministrationUrl")) { if ([string]::IsNullOrEmpty($CentralAdministrationUrl)) { $PSBoundParameters.Remove('CentralAdministrationUrl') | Out-Null } else { $uri = $CentralAdministrationUrl -as [System.Uri] if ($null -eq $uri.AbsoluteUri -or $uri.scheme -notin ('http', 'https')) { throw "CentralAdministrationUrl is not a valid URI. It should include the scheme (http/https) and address." } if ($CentralAdministrationUrl -match ':\d+') { throw "CentralAdministrationUrl should not specify port. Use CentralAdministrationPort instead." } } } $CurrentValues = Get-TargetResource @PSBoundParameters # Set default values to ensure they are passed to Invoke-SPDscCommand if (-not $PSBoundParameters.ContainsKey("CentralAdministrationPort")) { # If CentralAdministrationUrl is specified, let's infer the port from the Url if ($PSBoundParameters.ContainsKey("CentralAdministrationUrl")) { $CentralAdministrationPort = $PSBoundParameters.CentralAdministrationPort = (New-Object -TypeName System.Uri $CentralAdministrationUrl).Port } else { $CentralAdministrationPort = $PSBoundParameters.CentralAdministrationPort = 9999 } } if (-not $PSBoundParameters.ContainsKey("CentralAdministrationAuth")) { $CentralAdministrationAuth = $PSBoundParameters.CentralAdministrationAuth = "NTLM" } if ($CurrentValues.Ensure -eq "Present") { Write-Verbose -Message "Server already part of farm, updating settings" if ($CurrentValues.RunCentralAdmin -ne $RunCentralAdmin) { Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -ScriptBlock { $params = $args[0] # Provision central administration if ($params.RunCentralAdmin -eq $true) { Write-Verbose -Message "RunCentralAdmin set to true, provisioning Central Admin" $serviceInstance = Get-SPServiceInstance -Server $env:COMPUTERNAME if ($null -eq $serviceInstance) { $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain $fqdn = "$($env:COMPUTERNAME).$domain" $serviceInstance = Get-SPServiceInstance -Server $fqdn } if ($null -ne $serviceInstance) { $serviceInstance = $serviceInstance | Where-Object -FilterScript { $_.GetType().Name -eq "SPWebServiceInstance" -and $_.Name -eq "WSS_Administration" } } if ($null -eq $serviceInstance) { throw [Exception] "Unable to locate Central Admin service instance on this server" } Start-SPServiceInstance -Identity $serviceInstance } else { Write-Verbose -Message "RunCentralAdmin set to false, unprovisioning Central Admin" $serviceInstance = Get-SPServiceInstance -Server $env:COMPUTERNAME if ($null -eq $serviceInstance) { $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain $fqdn = "$($env:COMPUTERNAME).$domain" $serviceInstance = Get-SPServiceInstance -Server $fqdn } if ($null -ne $serviceInstance) { $serviceInstance = $serviceInstance | Where-Object -FilterScript { $_.GetType().Name -eq "SPWebServiceInstance" -and $_.Name -eq "WSS_Administration" } } if ($null -eq $serviceInstance) { throw "Unable to locate Central Admin service instance on this server" } Stop-SPServiceInstance -Identity $serviceInstance } } } if ($RunCentralAdmin) { # track whether or not we end up reprovisioning CA $reprovisionCentralAdmin = $false if ($PSBoundParameters.ContainsKey("CentralAdministrationUrl")) { # For the following scenarios, we should remove the CA web application and recreate it # CentralAdministrationUrl is passed in # AND Current CentralAdministrationUrl is not equal to new CentralAdministrationUrl # OR Current SecureBindings (HTTPS) or ServerBindings (HTTP) does not exist or doesn't # match desired url and port Write-Verbose -Message "Updating Central Admin URL configuration" Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -ScriptBlock { $params = $args[0] $centralAdminSite = Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication } $isCentralAdminUrlHttps = (([System.Uri]$params.CentralAdministrationUrl).Scheme -eq 'https') $desiredUri = [System.Uri]("{0}:{1}" -f $params.CentralAdministrationUrl.TrimEnd('/'), $params.CentralAdministrationPort) $currentUri = [System.Uri]$centralAdminSite.Url if ($desiredUri.AbsoluteUri -ne $currentUri.AbsoluteUri) { Write-Verbose -Message "Re-provisioning CA because $($currentUri.AbsoluteUri) does not equal $($desiredUri.AbsoluteUri)" $reprovisionCentralAdmin = $true } else { # check securebindings (https) or serverbindings (http) # there should be an entry in the SecureBindings object of the # SPWebApplication's IisSettings for the default zone $iisBindings = $null if ($isCentralAdminUrlHttps) { Write-Verbose -Message "Getting current secure bindings..." $iisBindings = $centralAdminSite.GetIisSettingsWithFallback("Default").SecureBindings } else { Write-Verbose -Message "Getting current server bindings..." $iisBindings = $centralAdminSite.GetIisSettingsWithFallback("Default").ServerBindings } if ($null -ne $iisBindings[0] -and (-not [string]::IsNullOrEmpty($iisBindings[0].HostHeader))) { # check to see if iisBindings host header and port match what we want them to be if ($desiredUri.Host -ne $iisBindings[0].HostHeader -or $desiredUri.Port -ne $iisBindings[0].Port) { Write-Verbose -Message "Re-provisioning CA because $($desiredUri.Host) does not equal $($iisBindings[0].HostHeader) or $($desiredUri.Port) does not equal $($iisBindings[0].Port)" $reprovisionCentralAdmin = $true } } else { # iisBindings did not exist or did not contain a valid hostheader Write-Verbose -Message "Re-provisioning CA because IIS Bindings does not exist or does not contain a valid host header" $reprovisionCentralAdmin = $true } } if ($reprovisionCentralAdmin) { # Write-Verbose -Message "Removing Central Admin web application in order to reprovision it" Remove-SPWebApplication -Identity $centralAdminSite.Url -Zone Default -DeleteIisSite $farm = Get-SPFarm $ca_service = $farm.Services | Where-Object -FilterScript { $_.TypeName -eq "Central Administration" } Write-Verbose -Message "Re-provisioning Central Admin web application" $webAppParams = @{ Identity = $centralAdminSite.Url Name = $ca_service.ApplicationPools.Name Zone = "Default" HostHeader = $desiredUri.Host Port = $desiredUri.Port AuthenticationMethod = $params.CentralAdministrationAuth SecureSocketsLayer = $isCentralAdminUrlHttps } New-SPWebApplicationExtension @webAppParams } } } elseif ($CurrentValues.CentralAdministrationPort -ne $CentralAdministrationPort) { Write-Verbose -Message "Updating CentralAdmin port to $CentralAdministrationPort" Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -ScriptBlock { $params = $args[0] Set-SPCentralAdministration -Port $params.CentralAdministrationPort } } # if Authentication Method doesn't match and we haven't reprovisioned CA above, update auth method if ($CurrentValues.CentralAdministrationAuth -ne $CentralAdministrationAuth -and (-not $reprovisionCentralAdmin)) { Write-Verbose -Message "Updating CentralAdmin authentication method from $($CurrentValues.CentralAdministrationAuth) to $CentralAdministrationAuth" Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -ScriptBlock { $params = $args[0] $centralAdminSite = Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication } $centralAdminSite | Set-SPWebApplication -Zone "Default" -AuthenticationMethod $params.CentralAdministrationAuth } } } if ($CurrentValues.DeveloperDashboard -ne $DeveloperDashboard) { Write-Verbose -Message "Updating DeveloperDashboard to $DeveloperDashboard" Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -ScriptBlock { $params = $args[0] Write-Verbose -Message "Updating Developer Dashboard setting" $admService = Get-SPDscContentService $developerDashboardSettings = $admService.DeveloperDashboardSettings $developerDashboardSettings.DisplayLevel = [Microsoft.SharePoint.Administration.SPDeveloperDashboardLevel]::$($params.DeveloperDashboard) $developerDashboardSettings.Update() } } return } else { Write-Verbose -Message "Server not part of farm, creating or joining farm" $actionResult = Invoke-SPDscCommand -Credential $InstallAccount ` -Arguments @($PSBoundParameters, $PSScriptRoot) ` -ScriptBlock { $params = $args[0] $scriptRoot = $args[1] $modulePath = "..\..\Modules\SharePointDsc.Farm\SPFarm.psm1" Import-Module -Name (Join-Path -Path $scriptRoot -ChildPath $modulePath -Resolve) if ($params.UseSQLAuthentication -eq $true) { Write-Verbose -Message "Using SQL authentication to create service application as `$useSQLAuthentication is set to $($params.useSQLAuthentication)." $databaseCredentialsParam = @{ DatabaseCredentials = $params.DatabaseCredentials } } else { $databaseCredentialsParam = "" } $sqlInstanceStatus = Get-SPDscSQLInstanceStatus -SQLServer $params.DatabaseServer @databaseCredentialsParam if ($sqlInstanceStatus.MaxDOPCorrect -ne $true) { throw "The MaxDOP setting is incorrect. Please correct before continuing." } $dbStatus = Get-SPDscConfigDBStatus -SQLServer $params.DatabaseServer ` -Database $params.FarmConfigDatabaseName ` @databaseCredentialsParam while ($dbStatus.Locked -eq $true) { Write-Verbose -Message ("[$([DateTime]::Now.ToShortTimeString())] The configuration " + "database is currently being provisioned by a remote " + "server, this server will wait for this to complete") Start-Sleep -Seconds 30 $dbStatus = Get-SPDscConfigDBStatus -SQLServer $params.DatabaseServer ` -Database $params.FarmConfigDatabaseName ` @databaseCredentialsParam } if ($dbStatus.ValidPermissions -eq $false) { throw "The current user does not have sufficient permissions to SQL Server" } $executeArgs = @{ DatabaseServer = $params.DatabaseServer DatabaseName = $params.FarmConfigDatabaseName Passphrase = $params.Passphrase.Password SkipRegisterAsDistributedCacheHost = $true } $supportsSettingApplicationCredentialKey = $false if ($params.useSQLAuthentication -eq $true) { Write-Verbose -Message "Using SQL authentication to connect to / create farm as `$useSQLAuthentication is set to $($params.useSQLAuthentication)." $executeArgs.Add("DatabaseCredentials", $params.DatabaseCredentials) } else { Write-Verbose -Message "`$useSQLAuthentication is false or not specified; using default Windows authentication." } $installedVersion = Get-SPDscInstalledProductVersion switch ($installedVersion.FileMajorPart) { 15 { Write-Verbose -Message "Detected Version: SharePoint 2013" } 16 { if ($params.ContainsKey("ServerRole") -eq $true) { if ($installedVersion.ProductBuildPart.ToString().Length -eq 4) { Write-Verbose -Message ("Detected Version: SharePoint 2016 - " + "configuring server as $($params.ServerRole)") } else { Write-Verbose -Message ("Detected Version: SharePoint 2019 - " + "configuring server as $($params.ServerRole)") $supportsSettingApplicationCredentialKey = $true } $executeArgs.Add("LocalServerRole", $params.ServerRole) } else { if ($installedVersion.ProductBuildPart.ToString().Length -eq 4) { Write-Verbose -Message ("Detected Version: SharePoint 2016 - no server " + "role provided, configuring server without a " + "specific role") } else { Write-Verbose -Message ("Detected Version: SharePoint 2019 - no server " + "role provided, configuring server without a " + "specific role") $supportsSettingApplicationCredentialKey = $true } $executeArgs.Add("ServerRoleOptional", $true) } } Default { throw [Exception] ("An unknown version of SharePoint (Major version $_) " + "was detected. Only versions 15 (SharePoint 2013) and" + "16 (SharePoint 2016 or SharePoint 2019) are supported.") } } if ($params.ContainsKey("ApplicationCredentialKey") -and -not $supportsSettingApplicationCredentialKey) { throw [Exception] ("Specifying ApplicationCredentialKey is only supported " + "on SharePoint 2019") } if ($dbStatus.DatabaseExists -eq $true) { Write-Verbose -Message ("The SharePoint config database " + "'$($params.FarmConfigDatabaseName)' already exists, so " + "this server will join the farm.") $createFarm = $false } elseif ($dbStatus.DatabaseExists -eq $false -and $params.RunCentralAdmin -eq $false) { # Only allow the farm to be created by a server that will run central admin # to avoid a ghost CA site appearing on this server and causing issues Write-Verbose -Message ("The SharePoint config database " + "'$($params.FarmConfigDatabaseName)' does not exist, but " + "this server will not be running the central admin " + "website, so it will wait to join the farm rather than " + "create one.") $createFarm = $false } else { Write-Verbose -Message ("The SharePoint config database " + "'$($params.FarmConfigDatabaseName)' does not exist, so " + "this server will create the farm.") $createFarm = $true } $farmAction = "" if ($createFarm -eq $false) { $dbStatus = Get-SPDscConfigDBStatus -SQLServer $params.DatabaseServer ` -Database $params.FarmConfigDatabaseName ` @databaseCredentialsParam $loopCount = 0 while ($dbStatus.DatabaseExists -eq $false -and $loopCount -lt 15) { Write-Verbose -Message ("The configuration database is not yet provisioned " + "by a remote server, this server will wait for up to " + "15 minutes for this to complete") Start-Sleep -Seconds 60 $loopCount++ $dbStatus = Get-SPDscConfigDBStatus -SQLServer $params.DatabaseServer ` -Database $params.FarmConfigDatabaseName ` @databaseCredentialsParam } Write-Verbose -Message "The database exists, so attempt to join the server to the farm" # Remove the server role optional attribute as it is only used when creating # a new farm if ($executeArgs.ContainsKey("ServerRoleOptional") -eq $true) { $executeArgs.Remove("ServerRoleOptional") } Write-Verbose -Message ("The server will attempt to join the farm now once every " + "60 seconds for the next 15 minutes.") $loopCount = 0 $connectedToFarm = $false $lastException = $null while ($connectedToFarm -eq $false -and $loopCount -lt 15) { try { Connect-SPConfigurationDatabase @executeArgs | Out-Null $connectedToFarm = $true } catch { $lastException = $_.Exception Write-Verbose -Message ("$([DateTime]::Now.ToShortTimeString()) - An error " + "occured joining config database " + "'$($params.FarmConfigDatabaseName)' on " + "'$($params.DatabaseServer)'. This resource will " + "wait and retry automatically for up to 15 minutes. " + "(waited $loopCount of 15 minutes)") $loopCount++ Start-Sleep -Seconds 60 } } if ($connectedToFarm -eq $false) { Write-Verbose -Message ("Unable to join config database. Throwing exception.") throw $lastException } $farmAction = "JoinedFarm" } else { Write-Verbose -Message "The database does not exist, so create a new farm" Write-Verbose -Message "Creating Lock database to prevent two servers creating the same farm" Add-SPDscConfigDBLock -SQLServer $params.DatabaseServer ` -Database $params.FarmConfigDatabaseName ` @databaseCredentialsParam try { $executeArgs += @{ FarmCredentials = $params.FarmAccount AdministrationContentDatabaseName = $params.AdminContentDatabaseName } Write-Verbose -Message "Creating new Config database" Write-Verbose -Message "executeArgs is:" foreach ($arg in $executeArgs.Keys) { Write-Verbose -Message "$arg $($executeArgs[$arg])" } New-SPConfigurationDatabase @executeArgs $farmAction = "CreatedFarm" } finally { Write-Verbose -Message "Removing Lock database" Remove-SPDscConfigDBLock -SQLServer $params.DatabaseServer ` -Database $params.FarmConfigDatabaseName ` @databaseCredentialsParam } } # Run common tasks for a new server Write-Verbose -Message "Starting Install-SPHelpCollection" Install-SPHelpCollection -All | Out-Null Write-Verbose -Message "Starting Initialize-SPResourceSecurity" Initialize-SPResourceSecurity | Out-Null Write-Verbose -Message "Starting Install-SPService" Install-SPService | Out-Null Write-Verbose -Message "Starting Install-SPFeature" Install-SPFeature -AllExistingFeatures -Force | Out-Null if ($params.ContainsKey("ApplicationCredentialKey")) { Write-Verbose -Message "Setting application credential key" Set-SPApplicationCredentialKey -Password $params.ApplicationCredentialKey.Password } # Provision central administration if ($params.RunCentralAdmin -eq $true) { Write-Verbose -Message "RunCentralAdmin is True, provisioning Central Admin" $centralAdminSite = Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication -eq $true } $centralAdminProvisioned = $false if ((New-Object -TypeName System.Uri $centralAdminSite.Url).Port -eq $params.CentralAdministrationPort) { $centralAdminProvisioned = $true } if ($centralAdminProvisioned -eq $false) { New-SPCentralAdministration -Port $params.CentralAdministrationPort ` -WindowsAuthProvider $params.CentralAdministrationAuth if (-not [string]::IsNullOrEmpty($params.CentralAdministrationUrl)) { $centralAdminSite = Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication -eq $true } # cases where we need to reprovision CA: # 1. desired Url is https # 2. desired Url/port does not match current Url/port # 3. IIS bindings don't match (shouldn't need this because case #2 should catch it in this case) $reprovisionCentralAdmin = $false $isCentralAdminUrlHttps = (([System.Uri]$params.CentralAdministrationUrl).Scheme -eq 'https') $desiredUri = [System.Uri]("{0}:{1}" -f $params.CentralAdministrationUrl.TrimEnd('/'), $params.CentralAdministrationPort) $currentUri = [System.Uri]$centralAdminSite.Url if ($isCentralAdminUrlHttps) { Write-Verbose -Message "Re-provisioning newly created CA because we want it to be HTTPS" $reprovisionCentralAdmin = $true } elseif ($desiredUri.AbsoluteUri -ne $currentUri.AbsoluteUri) { Write-Verbose -Message "Re-provisioning CA because $($currentUri.AbsoluteUri) does not equal $($desiredUri.AbsoluteUri)" $reprovisionCentralAdmin = $true } if ($reprovisionCentralAdmin) { Write-Verbose -Message "Removing Central Admin web application" # Wondering if -DeleteIisSite is necessary. Does this add more risk of ending up in # a state without CA or a way to recover it? Remove-SPWebApplication -Identity $centralAdminSite.Url -Zone Default -DeleteIisSite Write-Verbose -Message "Reprovisioning Central Admin with SSL" $webAppParams = @{ Identity = $centralAdminSite.Url Name = "SharePoint Central Administration v4" Zone = "Default" HostHeader = $desiredUri.Host Port = $desiredUri.Port AuthenticationMethod = $params.CentralAdministrationAuth SecureSocketsLayer = $isCentralAdminUrlHttps } New-SPWebApplicationExtension @webAppParams } } } else { $serviceInstance = Get-SPServiceInstance -Server $env:COMPUTERNAME if ($null -eq $serviceInstance) { $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain $fqdn = "$($env:COMPUTERNAME).$domain" $serviceInstance = Get-SPServiceInstance -Server $fqdn } if ($null -ne $serviceInstance) { $serviceInstance = $serviceInstance | Where-Object -FilterScript { $_.GetType().Name -eq "SPWebServiceInstance" -and $_.Name -eq "WSS_Administration" } } if ($null -eq $serviceInstance) { throw [Exception] "Unable to locate Central Admin service instance on this server" } Start-SPServiceInstance -Identity $serviceInstance } } Write-Verbose -Message "Starting Install-SPApplicationContent" Install-SPApplicationContent | Out-Null if ($params.ContainsKey("DeveloperDashboard") -and $params.DeveloperDashboard -ne "Off") { Write-Verbose -Message "Updating Developer Dashboard setting" $admService = Get-SPDscContentService $developerDashboardSettings = $admService.DeveloperDashboardSettings $developerDashboardSettings.DisplayLevel = [Microsoft.SharePoint.Administration.SPDeveloperDashboardLevel]::$params.DeveloperDashboard $developerDashboardSettings.Update() } return $farmAction } if ($actionResult -eq "JoinedFarm") { Write-Verbose -Message "Starting timer service" Start-Service -Name sptimerv4 Write-Verbose -Message ("Pausing for 5 minutes to allow the timer service to " + "fully provision the server") Start-Sleep -Seconds 300 Write-Verbose -Message ("Join farm complete. Restarting computer to allow " + "configuration to continue") $global:DSCMachineStatus = 1 } } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [ValidateSet('Yes')] [String] $IsSingleInstance, [Parameter()] [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [Parameter(Mandatory = $true)] [System.String] $FarmConfigDatabaseName, [Parameter(Mandatory = $true)] [System.String] $DatabaseServer, [Parameter()] [System.Boolean] $UseSQLAuthentication, [Parameter()] [System.Management.Automation.PSCredential] $DatabaseCredentials, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $FarmAccount, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Passphrase, [Parameter(Mandatory = $true)] [System.String] $AdminContentDatabaseName, [Parameter(Mandatory = $true)] [System.Boolean] $RunCentralAdmin, [Parameter()] [System.String] $CentralAdministrationUrl, [Parameter()] [ValidateRange(1, 65535)] [System.UInt32] $CentralAdministrationPort, [Parameter()] [System.String] [ValidateSet("NTLM", "Kerberos")] $CentralAdministrationAuth, [Parameter()] [System.String] [ValidateSet("Application", "ApplicationWithSearch", "Custom", "DistributedCache", "Search", "SingleServerFarm", "WebFrontEnd", "WebFrontEndWithDistributedCache")] $ServerRole, [Parameter()] [ValidateSet("Off", "On", "OnDemand")] [System.String] $DeveloperDashboard, [Parameter()] [System.Management.Automation.PSCredential] $ApplicationCredentialKey, [Parameter()] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Testing local SP Farm settings" $PSBoundParameters.Ensure = $Ensure if ($PSBoundParameters.ContainsKey("CentralAdministrationUrl")) { if ([string]::IsNullOrEmpty($CentralAdministrationUrl)) { $PSBoundParameters.Remove('CentralAdministrationUrl') | Out-Null } else { $uri = $CentralAdministrationUrl -as [System.Uri] if ($null -eq $uri.AbsoluteUri) { throw ("CentralAdministrationUrl is not a valid URI. It should " + "include the scheme (http/https) and address.") } # TODO: should we allow port here as long as either the port matches CentralAdministrationPort # or CentralAdministrationPort is not specified? if ($CentralAdministrationUrl -match ':\d+') { throw ("CentralAdministrationUrl should not specify port. Use " + "CentralAdministrationPort instead.") } } } $CurrentValues = Get-TargetResource @PSBoundParameters Write-Verbose -Message "Current Values: $(Convert-SPDscHashtableToString -Hashtable $CurrentValues)" Write-Verbose -Message "Target Values: $(Convert-SPDscHashtableToString -Hashtable $PSBoundParameters)" $result = Test-SPDscParameterState -CurrentValues $CurrentValues ` -Source $($MyInvocation.MyCommand.Source) ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @("Ensure", "RunCentralAdmin", "CentralAdministrationUrl", "CentralAdministrationPort", "CentralAdministrationAuth", "DeveloperDashboard") Write-Verbose -Message "Test-TargetResource returned $result" return $result } Export-ModuleMember -Function *-TargetResource |