DSCResources/MSFT_SqlSetup/MSFT_SqlSetup.psm1
Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) ` -ChildPath 'SqlServerDscHelper.psm1') Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` -ChildPath 'CommonResourceHelper.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlSetup' <# .SYNOPSIS Returns the current state of the SQL Server features. .PARAMETER Action The action to be performed. Default value is 'Install'. Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SourceCredential Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath`. If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER InstanceName Name of the SQL instance to be installed. .PARAMETER FailoverClusterNetworkName Host name to be assigned to the clustered SQL Server instance #> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter()] [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] [System.String] $Action = 'Install', [Parameter()] [System.String] $SourcePath, [Parameter()] [System.Management.Automation.PSCredential] $SourceCredential, [Parameter(Mandatory = $true)] [System.String] $InstanceName, [Parameter()] [System.String] $FailoverClusterNetworkName ) if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster','Addnode')) { $sqlHostName = $FailoverClusterNetworkName } else { $sqlHostName = $env:COMPUTERNAME } $InstanceName = $InstanceName.ToUpper() $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) if ($SourceCredential) { $newSmbMappingParameters = @{ RemotePath = $SourcePath UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" Password = $($SourceCredential.GetNetworkCredential().Password) } $null = New-SmbMapping @newSmbMappingParameters } $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' Write-Verbose -Message ($script:localizedData.UsingPath -f $pathToSetupExecutable) $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable if ($SourceCredential) { Remove-SmbMapping -RemotePath $SourcePath -Force } if ($InstanceName -eq 'MSSQLSERVER') { $databaseServiceName = 'MSSQLSERVER' $agentServiceName = 'SQLSERVERAGENT' $fullTextServiceName = 'MSSQLFDLauncher' $reportServiceName = 'ReportServer' $analysisServiceName = 'MSSQLServerOLAPService' } else { $databaseServiceName = "MSSQL`$$InstanceName" $agentServiceName = "SQLAgent`$$InstanceName" $fullTextServiceName = "MSSQLFDLauncher`$$InstanceName" $reportServiceName = "ReportServer`$$InstanceName" $analysisServiceName = "MSOLAP`$$InstanceName" } $integrationServiceName = "MsDtsServer$($sqlVersion)0" $features = '' $services = Get-Service Write-Verbose -Message $script:localizedData.EvaluateDatabaseEngineFeature if ($services | Where-Object {$_.Name -eq $databaseServiceName}) { Write-Verbose -Message $script:localizedData.DatabaseEngineFeatureFound $features += 'SQLENGINE,' $sqlServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$databaseServiceName'").StartName $agentServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$agentServiceName'").StartName $fullInstanceId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -Name $InstanceName).$InstanceName # Check if Replication sub component is configured for this instance $replicationRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstanceId\ConfigurationState" Write-Verbose -Message ($script:localizedData.EvaluateReplicationFeature -f $replicationRegistryPath) $isReplicationInstalled = (Get-ItemProperty -Path $replicationRegistryPath).SQL_Replication_Core_Inst if ($isReplicationInstalled -eq 1) { Write-Verbose -Message $script:localizedData.ReplicationFeatureFound $features += 'REPLICATION,' } else { Write-Verbose -Message $script:localizedData.ReplicationFeatureNotFound } # Check if Data Quality Client sub component is configured $dataQualityClientRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState" Write-Verbose -Message ($script:localizedData.EvaluateDataQualityClientFeature -f $dataQualityClientRegistryPath) $isDQCInstalled = (Get-ItemProperty -Path $dataQualityClientRegistryPath).SQL_DQ_CLIENT_Full if ($isDQCInstalled -eq 1) { Write-Verbose -Message $script:localizedData.DataQualityClientFeatureFound $features += 'DQC,' } else { Write-Verbose -Message $script:localizedData.DataQualityClientFeatureNotFound } # Check if Data Quality Services sub component is configured $dataQualityServicesRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\DQ\*" Write-Verbose -Message ($script:localizedData.EvaluateDataQualityServicesFeature -f $dataQualityServicesRegistryPath) $isDQInstalled = (Get-ItemProperty -Path $dataQualityServicesRegistryPath -ErrorAction SilentlyContinue) if ($isDQInstalled) { Write-Verbose -Message $script:localizedData.DataQualityServicesFeatureFound $features += 'DQ,' } else { Write-Verbose -Message $script:localizedData.DataQualityServicesFeatureNotFound } $instanceId = $fullInstanceId.Split('.')[1] $instanceDirectory = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstanceId\Setup" -Name 'SqlProgramDir').SqlProgramDir.Trim("\") $databaseServer = Connect-SQL -SQLServer $sqlHostName -SQLInstanceName $InstanceName $sqlCollation = $databaseServer.Collation $sqlSystemAdminAccounts = @() foreach ($sqlUser in $databaseServer.Logins) { foreach ($sqlRole in $sqlUser.ListMembers()) { if ($sqlRole -like 'sysadmin') { $sqlSystemAdminAccounts += $sqlUser.Name } } } if ($databaseServer.LoginMode -eq 'Mixed') { $securityMode = 'SQL' } else { $securityMode = 'Windows' } $installSQLDataDirectory = $databaseServer.InstallDataDirectory $sqlUserDatabaseDirectory = $databaseServer.DefaultFile $sqlUserDatabaseLogDirectory = $databaseServer.DefaultLog $sqlBackupDirectory = $databaseServer.BackupDirectory if ($databaseServer.IsClustered) { Write-Verbose -Message $script:localizedData.ClusterInstanceFound $clusteredSqlInstance = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | Where-Object { $_.PrivateProperties.InstanceName -eq $InstanceName } if (!$clusteredSqlInstance) { $errorMessage = $script:localizedData.FailoverClusterResourceNotFound -f $InstanceName New-ObjectNotFoundException -Message $errorMessage } Write-Verbose -Message $script:localizedData.FailoverClusterResourceFound $clusteredSqlGroup = $clusteredSqlInstance | Get-CimAssociatedInstance -ResultClassName MSCluster_ResourceGroup $clusteredSqlNetworkName = $clusteredSqlGroup | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | Where-Object { $_.Type -eq "Network Name" } $clusteredSqlIPAddress = ($clusteredSqlNetworkName | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | Where-Object { $_.Type -eq "IP Address" }).PrivateProperties.Address # Extract the required values $clusteredSqlGroupName = $clusteredSqlGroup.Name $clusteredSqlHostname = $clusteredSqlNetworkName.PrivateProperties.DnsName } else { Write-Verbose -Message $script:localizedData.ClusterInstanceNotFound } } else { Write-Verbose -Message $script:localizedData.DatabaseEngineFeatureNotFound } Write-Verbose -Message $script:localizedData.EvaluateFullTextFeature if ($services | Where-Object {$_.Name -eq $fullTextServiceName}) { Write-Verbose -Message $script:localizedData.FullTextFeatureFound $features += 'FULLTEXT,' $fullTextServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$fullTextServiceName'").StartName } else { Write-Verbose -Message $script:localizedData.FullTextFeatureNotFound } Write-Verbose -Message $script:localizedData.EvaluateReportingServicesFeature if ($services | Where-Object {$_.Name -eq $reportServiceName}) { Write-Verbose -Message $script:localizedData.ReportingServicesFeatureFound $features += 'RS,' $reportingServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$reportServiceName'").StartName } else { Write-Verbose -Message $script:localizedData.ReportingServicesFeatureNotFound } Write-Verbose -Message $script:localizedData.EvaluateAnalysisServicesFeature if ($services | Where-Object {$_.Name -eq $analysisServiceName}) { Write-Verbose -Message $script:localizedData.AnalysisServicesFeatureFound $features += 'AS,' $analysisServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$analysisServiceName'").StartName $analysisServer = Connect-SQLAnalysis -SQLServer $sqlHostName -SQLInstanceName $InstanceName $analysisCollation = $analysisServer.ServerProperties['CollationName'].Value $analysisDataDirectory = $analysisServer.ServerProperties['DataDir'].Value $analysisTempDirectory = $analysisServer.ServerProperties['TempDir'].Value $analysisLogDirectory = $analysisServer.ServerProperties['LogDir'].Value $analysisBackupDirectory = $analysisServer.ServerProperties['BackupDir'].Value $analysisSystemAdminAccounts = [System.String[]] $analysisServer.Roles['Administrators'].Members.Name $analysisConfigDirectory = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$analysisServiceName" -Name 'ImagePath').ImagePath.Replace(' -s ',',').Split(',')[1].Trim('"') } else { Write-Verbose -Message $script:localizedData.AnalysisServicesFeatureNotFound } Write-Verbose -Message $script:localizedData.EvaluateIntegrationServicesFeature if ($services | Where-Object {$_.Name -eq $integrationServiceName}) { Write-Verbose -Message $script:localizedData.IntegrationServicesFeatureFound $features += 'IS,' $integrationServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$integrationServiceName'").StartName } else { Write-Verbose -Message $script:localizedData.IntegrationServicesFeatureNotFound } # Check if Documentation Components "BOL" is configured $documentationComponentsRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState" Write-Verbose -Message ($script:localizedData.EvaluateDocumentationComponentsFeature -f $documentationComponentsRegistryPath) $isBOLInstalled = (Get-ItemProperty -Path $documentationComponentsRegistryPath -ErrorAction SilentlyContinue).SQL_BOL_Components if ($isBOLInstalled -eq 1) { Write-Verbose -Message $script:localizedData.DocumentationComponentsFeatureFound $features += 'BOL,' } else { Write-Verbose -Message $script:localizedData.DocumentationComponentsFeatureNotFound } $clientComponentsFullRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\Tools\Setup\Client_Components_Full" $registryClientComponentsFullFeatureList = (Get-ItemProperty -Path $clientComponentsFullRegistryPath -ErrorAction SilentlyContinue).FeatureList Write-Verbose -Message ($script:localizedData.EvaluateClientConnectivityToolsFeature -f $clientComponentsFullRegistryPath) if ($registryClientComponentsFullFeatureList -like '*Connectivity_FNS=3*') { Write-Verbose -Message $script:localizedData.ClientConnectivityToolsFeatureFound $features += 'CONN,' } else { Write-Verbose -Message $script:localizedData.ClientConnectivityToolsFeatureNotFound } Write-Verbose -Message ($script:localizedData.EvaluateClientConnectivityBackwardsCompatibilityToolsFeature -f $clientComponentsFullRegistryPath) if ($registryClientComponentsFullFeatureList -like '*Tools_Legacy_FNS=3*') { Write-Verbose -Message $script:localizedData.ClientConnectivityBackwardsCompatibilityToolsFeatureFound $features += 'BC,' } else { Write-Verbose -Message $script:localizedData.ClientConnectivityBackwardsCompatibilityToolsFeatureNotFound } Write-Verbose -Message ($script:localizedData.EvaluateClientToolsSdkFeature -f $clientComponentsFullRegistryPath) if (($registryClientComponentsFullFeatureList -like '*SDK_Full=3*') -and ($registryClientComponentsFullFeatureList -like '*SDK_FNS=3*')) { Write-Verbose -Message $script:localizedData.ClientToolsSdkFeatureFound $features += 'SDK,' } else { Write-Verbose -Message $script:localizedData.ClientToolsSdkFeatureNotFound } # Check if MDS sub component is configured for this server $masterDataServicesFullRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState" Write-Verbose -Message ($script:localizedData.EvaluateMasterDataServicesFeature -f $masterDataServicesFullRegistryPath) $isMDSInstalled = (Get-ItemProperty -Path $masterDataServicesFullRegistryPath -ErrorAction SilentlyContinue).MDSCoreFeature if ($isMDSInstalled -eq 1) { Write-Verbose -Message $script:localizedData.MasterDataServicesFeatureFound $features += 'MDS,' } else { Write-Verbose -Message $script:localizedData.MasterDataServicesFeatureNotFound } $registryUninstallPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' # Verify if SQL Server Management Studio 2008 or SQL Server Management Studio 2008 R2 (major version 10) is installed $installedProductSqlServerManagementStudio2008R2 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' ) -ErrorAction SilentlyContinue # Verify if SQL Server Management Studio 2012 (major version 11) is installed $installedProductSqlServerManagementStudio2012 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{A7037EB2-F953-4B12-B843-195F4D988DA1}' ) -ErrorAction SilentlyContinue # Verify if SQL Server Management Studio 2014 (major version 12) is installed $installedProductSqlServerManagementStudio2014 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{75A54138-3B98-4705-92E4-F619825B121F}' ) -ErrorAction SilentlyContinue if ( ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudio2008R2) -or ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudio2012) -or ($sqlVersion -eq 12 -and $installedProductSqlServerManagementStudio2014) ) { $features += 'SSMS,' } # Evaluating if SQL Server Management Studio Advanced 2008 or SQL Server Management Studio Advanced 2008 R2 (major version 10) is installed $installedProductSqlServerManagementStudioAdvanced2008R2 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' ) -ErrorAction SilentlyContinue # Evaluating if SQL Server Management Studio Advanced 2012 (major version 11) is installed $installedProductSqlServerManagementStudioAdvanced2012 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{7842C220-6E9A-4D5A-AE70-0E138271F883}' ) -ErrorAction SilentlyContinue # Evaluating if SQL Server Management Studio Advanced 2014 (major version 12) is installed $installedProductSqlServerManagementStudioAdvanced2014 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' ) -ErrorAction SilentlyContinue if ( ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudioAdvanced2008R2) -or ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudioAdvanced2012) -or ($sqlVersion -eq 12 -and $installedProductSqlServerManagementStudioAdvanced2014) ) { $features += 'ADV_SSMS,' } $features = $features.Trim(',') if ($features) { $registryInstallerComponentsPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components' switch ($sqlVersion) { { $_ -in ('10','11','12','13','14') } { $registryKeySharedDir = 'FEE2E540D20152D4597229B6CFBC0A69' $registryKeySharedWOWDir = 'A79497A344129F64CA7D69C56F5DD8B4' } } if ($registryKeySharedDir) { $installSharedDir = Get-FirstItemPropertyValue -Path (Join-Path -Path $registryInstallerComponentsPath -ChildPath $registryKeySharedDir) } if ($registryKeySharedWOWDir) { $installSharedWOWDir = Get-FirstItemPropertyValue -Path (Join-Path -Path $registryInstallerComponentsPath -ChildPath $registryKeySharedWOWDir) } } return @{ SourcePath = $SourcePath Features = $features InstanceName = $InstanceName InstanceID = $instanceID InstallSharedDir = $installSharedDir InstallSharedWOWDir = $installSharedWOWDir InstanceDir = $instanceDirectory SQLSvcAccountUsername = $sqlServiceAccountUsername AgtSvcAccountUsername = $agentServiceAccountUsername SQLCollation = $sqlCollation SQLSysAdminAccounts = $sqlSystemAdminAccounts SecurityMode = $securityMode InstallSQLDataDir = $installSQLDataDirectory SQLUserDBDir = $sqlUserDatabaseDirectory SQLUserDBLogDir = $sqlUserDatabaseLogDirectory SQLTempDBDir = $null SQLTempDBLogDir = $null SQLBackupDir = $sqlBackupDirectory FTSvcAccountUsername = $fullTextServiceAccountUsername RSSvcAccountUsername = $reportingServiceAccountUsername ASSvcAccountUsername = $analysisServiceAccountUsername ASCollation = $analysisCollation ASSysAdminAccounts = $analysisSystemAdminAccounts ASDataDir = $analysisDataDirectory ASLogDir = $analysisLogDirectory ASBackupDir = $analysisBackupDirectory ASTempDir = $analysisTempDirectory ASConfigDir = $analysisConfigDirectory ISSvcAccountUsername = $integrationServiceAccountUsername FailoverClusterGroupName = $clusteredSqlGroupName FailoverClusterNetworkName = $clusteredSqlHostname FailoverClusterIPAddress = $clusteredSqlIPAddress } } <# .SYNOPSIS Installs the SQL Server features to the node. .PARAMETER Action The action to be performed. Default value is 'Install'. Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SourceCredential Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath`. If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER SuppressReboot Suppressed reboot. .PARAMETER ForceReboot Forces reboot. .PARAMETER Features SQL features to be installed. .PARAMETER InstanceName Name of the SQL instance to be installed. .PARAMETER InstanceID SQL instance ID, if different from InstanceName. .PARAMETER ProductKey Product key for licensed installations. .PARAMETER UpdateEnabled Enabled updates during installation. .PARAMETER UpdateSource Path to the source of updates to be applied during installation. .PARAMETER SQMReporting Enable customer experience reporting. .PARAMETER ErrorReporting Enable error reporting. .PARAMETER InstallSharedDir Installation path for shared SQL files. .PARAMETER InstallSharedWOWDir Installation path for x86 shared SQL files. .PARAMETER InstanceDir Installation path for SQL instance files. .PARAMETER SQLSvcAccount Service account for the SQL service. .PARAMETER AgtSvcAccount Service account for the SQL Agent service. .PARAMETER SQLCollation Collation for SQL. .PARAMETER SQLSysAdminAccounts Array of accounts to be made SQL administrators. .PARAMETER SecurityMode Security mode to apply to the SQL Server instance. .PARAMETER SAPwd SA password, if SecurityMode is set to 'SQL'. .PARAMETER InstallSQLDataDir Root path for SQL database files. .PARAMETER SQLUserDBDir Path for SQL database files. .PARAMETER SQLUserDBLogDir Path for SQL log files. .PARAMETER SQLTempDBDir Path for SQL TempDB files. .PARAMETER SQLTempDBLogDir Path for SQL TempDB log files. .PARAMETER SQLBackupDir Path for SQL backup files. .PARAMETER FTSvcAccount Service account for the Full Text service. .PARAMETER RSSvcAccount Service account for Reporting Services service. .PARAMETER ASSvcAccount Service account for Analysis Services service. .PARAMETER ASCollation Collation for Analysis Services. .PARAMETER ASSysAdminAccounts Array of accounts to be made Analysis Services admins. .PARAMETER ASDataDir Path for Analysis Services data files. .PARAMETER ASLogDir Path for Analysis Services log files. .PARAMETER ASBackupDir Path for Analysis Services backup files. .PARAMETER ASTempDir Path for Analysis Services temp files. .PARAMETER ASConfigDir Path for Analysis Services config. .PARAMETER ISSvcAccount Service account for Integration Services service. .PARAMETER BrowserSvcStartupType Specifies the startup mode for SQL Server Browser service .PARAMETER FailoverClusterGroupName The name of the resource group to create for the clustered SQL Server instance. Default is 'SQL Server (InstanceName)'. .PARAMETER FailoverClusterIPAddress Array of IP Addresses to be assigned to the clustered SQL Server instance .PARAMETER FailoverClusterNetworkName Host name to be assigned to the clustered SQL Server instance .PARAMETER SetupProcessTimeout The timeout, in seconds, to wait for the setup process to finish. Default value is 7200 seconds (2 hours). If the setup process does not finish before this time, and error will be thrown. #> function Set-TargetResource { # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot, either by force or when there are pending changes. [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] [CmdletBinding()] param ( [Parameter()] [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] [System.String] $Action = 'Install', [Parameter()] [System.String] $SourcePath, [Parameter()] [System.Management.Automation.PSCredential] $SourceCredential, [Parameter()] [System.Boolean] $SuppressReboot, [Parameter()] [System.Boolean] $ForceReboot, [Parameter()] [System.String] $Features, [Parameter(Mandatory = $true)] [System.String] $InstanceName, [Parameter()] [System.String] $InstanceID, [Parameter()] [System.String] $ProductKey, [Parameter()] [System.String] $UpdateEnabled, [Parameter()] [System.String] $UpdateSource, [Parameter()] [System.String] $SQMReporting, [Parameter()] [System.String] $ErrorReporting, [Parameter()] [System.String] $InstallSharedDir, [Parameter()] [System.String] $InstallSharedWOWDir, [Parameter()] [System.String] $InstanceDir, [Parameter()] [System.Management.Automation.PSCredential] $SQLSvcAccount, [Parameter()] [System.Management.Automation.PSCredential] $AgtSvcAccount, [Parameter()] [System.String] $SQLCollation, [Parameter()] [System.String[]] $SQLSysAdminAccounts, [Parameter()] [System.String] $SecurityMode, [Parameter()] [System.Management.Automation.PSCredential] $SAPwd, [Parameter()] [System.String] $InstallSQLDataDir, [Parameter()] [System.String] $SQLUserDBDir, [Parameter()] [System.String] $SQLUserDBLogDir, [Parameter()] [System.String] $SQLTempDBDir, [Parameter()] [System.String] $SQLTempDBLogDir, [Parameter()] [System.String] $SQLBackupDir, [Parameter()] [System.Management.Automation.PSCredential] $FTSvcAccount, [Parameter()] [System.Management.Automation.PSCredential] $RSSvcAccount, [Parameter()] [System.Management.Automation.PSCredential] $ASSvcAccount, [Parameter()] [System.String] $ASCollation, [Parameter()] [System.String[]] $ASSysAdminAccounts, [Parameter()] [System.String] $ASDataDir, [Parameter()] [System.String] $ASLogDir, [Parameter()] [System.String] $ASBackupDir, [Parameter()] [System.String] $ASTempDir, [Parameter()] [System.String] $ASConfigDir, [Parameter()] [System.Management.Automation.PSCredential] $ISSvcAccount, [Parameter()] [System.String] [ValidateSet('Automatic', 'Disabled', 'Manual')] $BrowserSvcStartupType, [Parameter()] [System.String] $FailoverClusterGroupName = "SQL Server ($InstanceName)", [Parameter()] [System.String[]] $FailoverClusterIPAddress, [Parameter()] [System.String] $FailoverClusterNetworkName, [Parameter()] [System.UInt32] $SetupProcessTimeout = 7200 ) $getTargetResourceParameters = @{ Action = $Action SourcePath = $SourcePath SourceCredential = $SourceCredential InstanceName = $InstanceName FailoverClusterNetworkName = $FailoverClusterNetworkName } $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters $InstanceName = $InstanceName.ToUpper() $parametersToEvaluateTrailingSlash = @( 'InstanceDir', 'InstallSharedDir', 'InstallSharedWOWDir', 'InstallSQLDataDir', 'SQLUserDBDir', 'SQLUserDBLogDir', 'SQLTempDBDir', 'SQLTempDBLogDir', 'SQLBackupDir', 'ASDataDir', 'ASLogDir', 'ASBackupDir', 'ASTempDir', 'ASConfigDir', 'UpdateSource' ) # Remove trailing slash ('\') from paths foreach ($parameterName in $parametersToEvaluateTrailingSlash) { if ($PSBoundParameters.ContainsKey($parameterName)) { $parameterValue = Get-Variable -Name $parameterName -ValueOnly # Trim backslash, but only if the path contains a full path and not just a qualifier. if ($parameterValue -and $parameterValue -notmatch '^[a-zA-Z]:\\$') { Set-Variable -Name $parameterName -Value $parameterValue.TrimEnd('\') } # If the path only contains a qualifier but no backslash ('M:'), then a backslash is added ('M:\'). if ($parameterValue -match '^[a-zA-Z]:$') { Set-Variable -Name $parameterName -Value "$parameterValue\" } } } $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) if ($SourceCredential) { $newSmbMappingParameters = @{ RemotePath = $SourcePath UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" Password = $($SourceCredential.GetNetworkCredential().Password) } $null = New-SmbMapping @newSmbMappingParameters # Create a destination folder so the media files aren't written to the root of the Temp folder. $mediaDestinationFolder = Split-Path -Path $SourcePath -Leaf if (-not $mediaDestinationFolder ) { $mediaDestinationFolder = New-Guid | Select-Object -ExpandProperty Guid } $mediaDestinationPath = Join-Path -Path (Get-TemporaryFolder) -ChildPath $mediaDestinationFolder Write-Verbose -Message ($script:localizedData.RobocopyIsCopying -f $SourcePath, $mediaDestinationPath) Copy-ItemWithRobocopy -Path $SourcePath -DestinationPath $mediaDestinationPath Remove-SmbMapping -RemotePath $SourcePath -Force $SourcePath = $mediaDestinationPath } $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' Write-Verbose -Message ($script:localizedData.UsingPath -f $pathToSetupExecutable) $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable # Determine features to install $featuresToInstall = '' foreach ($feature in $Features.Split(',')) { # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase $feature = $feature.ToUpper(); if (($sqlVersion -in ('13','14')) -and ($feature -in ('ADV_SSMS','SSMS'))) { $errorMessage = $script:localizedData.FeatureNotSupported -f $feature New-InvalidOperationException -Message $errorMessage } if (-not ($getTargetResourceResult.Features.Contains($feature))) { $featuresToInstall += "$feature," } } $Features = $featuresToInstall.Trim(',') # If SQL shared components already installed, clear InstallShared*Dir variables switch ($sqlVersion) { { $_ -in ('10','11','12','13','14') } { if((Get-Variable -Name 'InstallSharedDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedDir' -Value '' } if((Get-Variable -Name 'InstallSharedWOWDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedWOWDir' -Value '' } } } $setupArguments = @{} if ($Action -in @('PrepareFailoverCluster','CompleteFailoverCluster','InstallFailoverCluster','Addnode')) { # This was brought over from the old module. Should be removed (breaking change). $setupArguments += @{ SkipRules = 'Cluster_VerifyForErrors' } } <# Set the failover cluster group name and failover cluster network name for this clustered instance if the action is either installing (InstallFailoverCluster) or completing (CompleteFailoverCluster) a cluster. #> if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) { $setupArguments += @{ FailoverClusterNetworkName = $FailoverClusterNetworkName FailoverClusterGroup = $FailoverClusterGroupName } } # Perform disk mapping for specific cluster installation types if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) { $requiredDrive = @() # This is also used to evaluate which cluster shard disks should be used. $parametersToEvaluateShareDisk = @( 'InstallSQLDataDir', 'SQLUserDBDir', 'SQLUserDBLogDir', 'SQLTempDBDir', 'SQLTempDBLogDir', 'SQLBackupDir', 'ASDataDir', 'ASLogDir', 'ASBackupDir', 'ASTempDir', 'ASConfigDir' ) # Get a required listing of drives based on parameters assigned by user. foreach ($parameterName in $parametersToEvaluateShareDisk) { if ($PSBoundParameters.ContainsKey($parameterName)) { $parameterValue = Get-Variable -Name $parameterName -ValueOnly if ($parameterValue) { Write-Verbose -Message ($script:localizedData.PathRequireClusterDriveFound -f $parameterName, $parameterValue) $requiredDrive += $parameterValue } } } # Only keep unique paths and add a member to keep track if the path is mapped to a disk. $requiredDrive = $requiredDrive | Sort-Object -Unique | Add-Member -MemberType NoteProperty -Name IsMapped -Value $false -PassThru # Get the disk resources that are available (not assigned to a cluster role) $availableStorage = Get-CimInstance -Namespace 'root/MSCluster' -ClassName 'MSCluster_ResourceGroup' -Filter "Name = 'Available Storage'" | Get-CimAssociatedInstance -Association MSCluster_ResourceGroupToResource -ResultClassName MSCluster_Resource | ` Add-Member -MemberType NoteProperty -Name 'IsPossibleOwner' -Value $false -PassThru # First map regular cluster volumes foreach ($diskResource in $availableStorage) { # Determine whether the current node is a possible owner of the disk resource $possibleOwners = $diskResource | Get-CimAssociatedInstance -Association 'MSCluster_ResourceToPossibleOwner' -KeyOnly | Select-Object -ExpandProperty Name if ($possibleOwners -icontains $env:COMPUTERNAME) { $diskResource.IsPossibleOwner = $true } } $failoverClusterDisks = @() foreach ($currentRequiredDrive in $requiredDrive) { foreach ($diskResource in ($availableStorage | Where-Object {$_.IsPossibleOwner -eq $true})) { $partitions = $diskResource | Get-CimAssociatedInstance -ResultClassName 'MSCluster_DiskPartition' | Select-Object -ExpandProperty Path foreach ($partition in $partitions) { if ($currentRequiredDrive -imatch $partition.Replace('\','\\')) { $currentRequiredDrive.IsMapped = $true $failoverClusterDisks += $diskResource.Name break } if ($currentRequiredDrive.IsMapped) { break } } if ($currentRequiredDrive.IsMapped) { break } } } # Now we handle cluster shared volumes $clusterSharedVolumes = Get-CimInstance -ClassName 'MSCluster_ClusterSharedVolume' -Namespace 'root/MSCluster' foreach ($clusterSharedVolume in $clusterSharedVolumes) { foreach ($currentRequiredDrive in ($requiredDrive | Where-Object {$_.IsMapped -eq $false})) { if ($currentRequiredDrive -imatch $clusterSharedVolume.Name.Replace('\','\\')) { $diskName = Get-CimInstance -ClassName 'MSCluster_ClusterSharedVolumeToResource' -Namespace 'root/MSCluster' | ` Where-Object {$_.GroupComponent.Name -eq $clusterSharedVolume.Name} | ` Select-Object -ExpandProperty PartComponent | ` Select-Object -ExpandProperty Name $failoverClusterDisks += $diskName $currentRequiredDrive.IsMapped = $true } } } # Ensure we have a unique listing of disks $failoverClusterDisks = $failoverClusterDisks | Sort-Object -Unique # Ensure we mapped all required drives $unMappedRequiredDrives = $requiredDrive | Where-Object {$_.IsMapped -eq $false} | Measure-Object if ($unMappedRequiredDrives.Count -gt 0) { $errorMessage = $script:localizedData.FailoverClusterDiskMappingError -f ($failoverClusterDisks -join '; ') New-InvalidResultException -Message $errorMessage } # Add the cluster disks as a setup argument $setupArguments += @{ FailoverClusterDisks = ($failoverClusterDisks | Sort-Object) } } # Determine network mapping for specific cluster installation types if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) { $clusterIPAddresses = @() # If no IP Address has been specified, use "DEFAULT" if ($FailoverClusterIPAddress.Count -eq 0) { $clusterIPAddresses += "DEFAULT" } else { # Get the available client networks $availableNetworks = @(Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Network -Filter 'Role >= 2') # Add supplied IP Addresses that are valid for available cluster networks foreach ($address in $FailoverClusterIPAddress) { foreach ($network in $availableNetworks) { # Determine whether the IP address is valid for this network if (Test-IPAddress -IPAddress $address -NetworkID $network.Address -SubnetMask $network.AddressMask) { # Add the formatted string to our array $clusterIPAddresses += "IPv4;$address;$($network.Name);$($network.AddressMask)" } } } } # Ensure we mapped all required networks $suppliedNetworkCount = $FailoverClusterIPAddress.Count $mappedNetworkCount = $clusterIPAddresses.Count # Determine whether we have mapping issues for the IP Address(es) if ($mappedNetworkCount -lt $suppliedNetworkCount) { $errorMessage = $script:localizedData.FailoverClusterIPAddressNotValid New-InvalidResultException -Message $errorMessage } # Add the networks to the installation arguments $setupArguments += @{ FailoverClusterIPAddresses = $clusterIPAddresses } } # Add standard install arguments $setupArguments += @{ Quiet = $true IAcceptSQLServerLicenseTerms = $true Action = $Action } $argumentVars = @( 'InstanceName', 'InstanceID', 'UpdateEnabled', 'UpdateSource', 'ProductKey', 'SQMReporting', 'ErrorReporting' ) if ($Action -in @('Install','InstallFailoverCluster','PrepareFailoverCluster','CompleteFailoverCluster')) { $argumentVars += @( 'Features', 'InstallSharedDir', 'InstallSharedWOWDir', 'InstanceDir' ) } if ($null -ne $BrowserSvcStartupType) { $argumentVars += 'BrowserSvcStartupType' } if ($Features.Contains('SQLENGINE')) { if ($PSBoundParameters.ContainsKey('SQLSvcAccount')) { $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $SQLSvcAccount -ServiceType 'SQL') } if($PSBoundParameters.ContainsKey('AgtSvcAccount')) { $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $AgtSvcAccount -ServiceType 'AGT') } if ($SecurityMode -eq 'SQL') { $setupArguments += @{ SAPwd = $SAPwd.GetNetworkCredential().Password } } # Should not be passed when PrepareFailoverCluster is specified if ($Action -in @('Install','InstallFailoverCluster','CompleteFailoverCluster')) { if ($null -ne $PsDscContext.RunAsUser) { <# Add the credentials from the parameter PsDscRunAsCredential, as the first system administrator. The username is stored in $PsDscContext.RunAsUser. #> Write-Verbose -Message ($script:localizedData.AddingFirstSystemAdministratorSqlServer -f $($PsDscContext.RunAsUser)) $setupArguments += @{ SQLSysAdminAccounts = @($PsDscContext.RunAsUser) } } if ($PSBoundParameters.ContainsKey('SQLSysAdminAccounts')) { $setupArguments['SQLSysAdminAccounts'] += $SQLSysAdminAccounts } $argumentVars += @( 'SecurityMode', 'SQLCollation', 'InstallSQLDataDir', 'SQLUserDBDir', 'SQLUserDBLogDir', 'SQLTempDBDir', 'SQLTempDBLogDir', 'SQLBackupDir' ) } if ($Action -in @('Install')) { $setupArguments += @{ AgtSvcStartupType = 'Automatic' } } } if ($Features.Contains('FULLTEXT')) { if ($PSBoundParameters.ContainsKey('FTSvcAccount')) { $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $FTSvcAccount -ServiceType 'FT') } } if ($Features.Contains('RS')) { if ($PSBoundParameters.ContainsKey('RSSvcAccount')) { $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $RSSvcAccount -ServiceType 'RS') } } if ($Features.Contains('AS')) { $argumentVars += @( 'ASCollation', 'ASDataDir', 'ASLogDir', 'ASBackupDir', 'ASTempDir', 'ASConfigDir' ) if ($PSBoundParameters.ContainsKey('ASSvcAccount')) { $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ASSvcAccount -ServiceType 'AS') } if ($Action -in ('Install','InstallFailoverCluster','CompleteFailoverCluster')) { if ($null -ne $PsDscContext.RunAsUser) { <# Add the credentials from the parameter PsDscRunAsCredential, as the first system administrator. The username is stored in $PsDscContext.RunAsUser. #> Write-Verbose -Message ($script:localizedData.AddingFirstSystemAdministratorAnalysisServices -f $($PsDscContext.RunAsUser)) $setupArguments += @{ ASSysAdminAccounts = @($PsDscContext.RunAsUser) } } if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) { $setupArguments['ASSysAdminAccounts'] += $ASSysAdminAccounts } } } if ($Features.Contains('IS')) { if ($PSBoundParameters.ContainsKey('ISSvcAccount')) { $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ISSvcAccount -ServiceType 'IS') } } # Automatically include any additional arguments foreach ($argument in $argumentVars) { if($argument -eq 'ProductKey') { $setupArguments += @{ 'PID' = (Get-Variable -Name $argument -ValueOnly) } } else { # If the argument contains a value, then add the argument to the setup argument list if (Get-Variable -Name $argument -ValueOnly) { $setupArguments += @{ $argument = (Get-Variable -Name $argument -ValueOnly) } } } } # Build the argument string to be passed to setup $arguments = '' foreach ($currentSetupArgument in $setupArguments.GetEnumerator()) { if ($currentSetupArgument.Value -ne '') { # Arrays are handled specially if ($currentSetupArgument.Value -is [array]) { # Sort and format the array $setupArgumentValue = ($currentSetupArgument.Value | Sort-Object | ForEach-Object { '"{0}"' -f $_ }) -join ' ' } elseif ($currentSetupArgument.Value -is [Boolean]) { $setupArgumentValue = @{ $true = 'True'; $false = 'False' }[$currentSetupArgument.Value] $setupArgumentValue = '"{0}"' -f $setupArgumentValue } else { # Features are comma-separated, no quotes if ($currentSetupArgument.Key -eq 'Features') { $setupArgumentValue = $currentSetupArgument.Value } else { $setupArgumentValue = '"{0}"' -f $currentSetupArgument.Value } } $arguments += "/$($currentSetupArgument.Key.ToUpper())=$($setupArgumentValue) " } } # Replace sensitive values for verbose output $log = $arguments if ($SecurityMode -eq 'SQL') { $log = $log.Replace($SAPwd.GetNetworkCredential().Password,"********") } if ($ProductKey -ne "") { $log = $log.Replace($ProductKey,"*****-*****-*****-*****-*****") } $logVars = @('AgtSvcAccount', 'SQLSvcAccount', 'FTSvcAccount', 'RSSvcAccount', 'ASSvcAccount','ISSvcAccount') foreach ($logVar in $logVars) { if ($PSBoundParameters.ContainsKey($logVar)) { $log = $log.Replace((Get-Variable -Name $logVar).Value.GetNetworkCredential().Password,"********") } } $arguments = $arguments.Trim() try { Write-Verbose -Message ($script:localizedData.SetupArguments -f $log) <# This handles when PsDscRunAsCredential is set, or running as the SYSTEM account (when PsDscRunAsCredential is not set). #> $startProcessParameters = @{ FilePath = $pathToSetupExecutable ArgumentList = $arguments Timeout = $SetupProcessTimeout } $processExitCode = Start-SqlSetupProcess @startProcessParameters $setupExitMessage = ($script:localizedData.SetupExitMessage -f $processExitCode) if ($processExitCode -eq 3010 -and -not $SuppressReboot) { $setupExitMessageRebootRequired = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupSuccessfulRebootRequired)) Write-Warning -Message $setupExitMessageRebootRequired } elseif ($processExitCode -ne 0) { $setupExitMessageError = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupFailed)) Write-Warning $setupExitMessageError } else { $setupExitMessageSuccessful = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupSuccessful)) Write-Verbose -Message $setupExitMessageSuccessful } if ($ForceReboot -or ($null -ne (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue))) { if (-not ($SuppressReboot)) { Write-Verbose -Message $script:localizedData.Reboot $global:DSCMachineStatus = 1 } else { Write-Verbose -Message $script:localizedData.SuppressReboot } } if (-not (Test-TargetResource @PSBoundParameters)) { $errorMessage = $script:localizedData.TestFailedAfterSet New-InvalidResultException -Message $errorMessage } } catch { throw $_ } } <# .SYNOPSIS Tests if the SQL Server features are installed on the node. .PARAMETER Action The action to be performed. Default value is 'Install'. Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SourceCredential Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath`. If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER SuppressReboot Suppresses reboot. .PARAMETER ForceReboot Forces reboot. .PARAMETER Features SQL features to be installed. .PARAMETER InstanceName Name of the SQL instance to be installed. .PARAMETER InstanceID SQL instance ID, if different from InstanceName. .PARAMETER ProductKey Product key for licensed installations. .PARAMETER UpdateEnabled Enabled updates during installation. .PARAMETER UpdateSource Path to the source of updates to be applied during installation. .PARAMETER SQMReporting Enable customer experience reporting. .PARAMETER ErrorReporting Enable error reporting. .PARAMETER InstallSharedDir Installation path for shared SQL files. .PARAMETER InstallSharedWOWDir Installation path for x86 shared SQL files. .PARAMETER InstanceDir Installation path for SQL instance files. .PARAMETER SQLSvcAccount Service account for the SQL service. .PARAMETER AgtSvcAccount Service account for the SQL Agent service. .PARAMETER SQLCollation Collation for SQL. .PARAMETER SQLSysAdminAccounts Array of accounts to be made SQL administrators. .PARAMETER SecurityMode Security mode to apply to the SQL Server instance. .PARAMETER SAPwd SA password, if SecurityMode is set to 'SQL'. .PARAMETER InstallSQLDataDir Root path for SQL database files. .PARAMETER SQLUserDBDir Path for SQL database files. .PARAMETER SQLUserDBLogDir Path for SQL log files. .PARAMETER SQLTempDBDir Path for SQL TempDB files. .PARAMETER SQLTempDBLogDir Path for SQL TempDB log files. .PARAMETER SQLBackupDir Path for SQL backup files. .PARAMETER FTSvcAccount Service account for the Full Text service. .PARAMETER RSSvcAccount Service account for Reporting Services service. .PARAMETER ASSvcAccount Service account for Analysis Services service. .PARAMETER ASCollation Collation for Analysis Services. .PARAMETER ASSysAdminAccounts Array of accounts to be made Analysis Services admins. .PARAMETER ASDataDir Path for Analysis Services data files. .PARAMETER ASLogDir Path for Analysis Services log files. .PARAMETER ASBackupDir Path for Analysis Services backup files. .PARAMETER ASTempDir Path for Analysis Services temp files. .PARAMETER ASConfigDir Path for Analysis Services config. .PARAMETER ISSvcAccount Service account for Integration Services service. .PARAMETER BrowserSvcStartupType Specifies the startup mode for SQL Server Browser service .PARAMETER FailoverClusterGroupName The name of the resource group to create for the clustered SQL Server instance. Default is 'SQL Server (InstanceName)'. .PARAMETER FailoverClusterIPAddress Array of IP Addresses to be assigned to the clustered SQL Server instance .PARAMETER FailoverClusterNetworkName Host name to be assigned to the clustered SQL Server instance .PARAMETER SetupProcessTimeout The timeout, in seconds, to wait for the setup process to finish. Default value is 7200 seconds (2 hours). If the setup process does not finish before this time, and error will be thrown. #> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter()] [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] [System.String] $Action = 'Install', [Parameter()] [System.String] $SourcePath, [Parameter()] [System.Management.Automation.PSCredential] $SourceCredential, [Parameter()] [System.Boolean] $SuppressReboot, [Parameter()] [System.Boolean] $ForceReboot, [Parameter()] [System.String] $Features, [Parameter(Mandatory = $true)] [System.String] $InstanceName, [Parameter()] [System.String] $InstanceID, [Parameter()] [System.String] $ProductKey, [Parameter()] [System.String] $UpdateEnabled, [Parameter()] [System.String] $UpdateSource, [Parameter()] [System.String] $SQMReporting, [Parameter()] [System.String] $ErrorReporting, [Parameter()] [System.String] $InstallSharedDir, [Parameter()] [System.String] $InstallSharedWOWDir, [Parameter()] [System.String] $InstanceDir, [Parameter()] [System.Management.Automation.PSCredential] $SQLSvcAccount, [Parameter()] [System.Management.Automation.PSCredential] $AgtSvcAccount, [Parameter()] [System.String] $SQLCollation, [Parameter()] [System.String[]] $SQLSysAdminAccounts, [Parameter()] [System.String] $SecurityMode, [Parameter()] [System.Management.Automation.PSCredential] $SAPwd, [Parameter()] [System.String] $InstallSQLDataDir, [Parameter()] [System.String] $SQLUserDBDir, [Parameter()] [System.String] $SQLUserDBLogDir, [Parameter()] [System.String] $SQLTempDBDir, [Parameter()] [System.String] $SQLTempDBLogDir, [Parameter()] [System.String] $SQLBackupDir, [Parameter()] [System.Management.Automation.PSCredential] $FTSvcAccount, [Parameter()] [System.Management.Automation.PSCredential] $RSSvcAccount, [Parameter()] [System.Management.Automation.PSCredential] $ASSvcAccount, [Parameter()] [System.String] $ASCollation, [Parameter()] [System.String[]] $ASSysAdminAccounts, [Parameter()] [System.String] $ASDataDir, [Parameter()] [System.String] $ASLogDir, [Parameter()] [System.String] $ASBackupDir, [Parameter()] [System.String] $ASTempDir, [Parameter()] [System.String] $ASConfigDir, [Parameter()] [System.Management.Automation.PSCredential] $ISSvcAccount, [Parameter()] [System.String] [ValidateSet('Automatic', 'Disabled', 'Manual')] $BrowserSvcStartupType, [Parameter(ParameterSetName = 'ClusterInstall')] [System.String] $FailoverClusterGroupName = "SQL Server ($InstanceName)", [Parameter(ParameterSetName = 'ClusterInstall')] [System.String[]] $FailoverClusterIPAddress, [Parameter(ParameterSetName = 'ClusterInstall')] [System.String] $FailoverClusterNetworkName, [Parameter()] [System.UInt32] $SetupProcessTimeout = 7200 ) $getTargetResourceParameters = @{ Action = $Action SourcePath = $SourcePath SourceCredential = $SourceCredential InstanceName = $InstanceName FailoverClusterNetworkName = $FailoverClusterNetworkName } $boundParameters = $PSBoundParameters $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters Write-Verbose -Message ($script:localizedData.FeaturesFound -f $($getTargetResourceResult.Features)) $result = $true if ($getTargetResourceResult.Features ) { foreach ($feature in $Features.Split(",")) { # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase $feature = $feature.ToUpper(); if(!($getTargetResourceResult.Features.Contains($feature))) { Write-Verbose -Message ($script:localizedData.UnableToFindFeature -f $feature, $($getTargetResourceResult.Features)) $result = $false } } } else { $result = $false } if ($PSCmdlet.ParameterSetName -eq 'ClusterInstall') { Write-Verbose -Message $script:localizedData.EvaluatingClusterParameters $boundParameters.Keys | Where-Object {$_ -imatch "^FailoverCluster"} | ForEach-Object { $variableName = $_ if ($getTargetResourceResult.$variableName -ne $boundParameters[$variableName]) { Write-Verbose -Message ($script:localizedData.ClusterParameterIsNotInDesiredState -f $variableName, $($boundParameters[$variableName])) $result = $false } } } return $result } <# .SYNOPSIS Returns the SQL Server major version from the setup.exe executable provided in the Path parameter. .PARAMETER Path String containing the path to the SQL Server setup.exe executable. #> function Get-SqlMajorVersion { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Path ) (Get-Item -Path $Path).VersionInfo.ProductVersion.Split('.')[0] } <# .SYNOPSIS Returns the first item value in the registry location provided in the Path parameter. .PARAMETER Path String containing the path to the registry. #> function Get-FirstItemPropertyValue { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Path ) $registryProperty = Get-Item -Path $Path -ErrorAction SilentlyContinue if ($registryProperty) { $registryProperty = $registryProperty | Select-Object -ExpandProperty Property | Select-Object -First 1 if ($registryProperty) { $registryPropertyValue = (Get-ItemProperty -Path $Path -Name $registryProperty).$registryProperty.TrimEnd('\') } } return $registryPropertyValue } <# .SYNOPSIS Copy folder structure using Robocopy. Every file and folder, including empty ones are copied. .PARAMETER Path Source path to be copied. .PARAMETER DestinationPath The path to the destination. #> function Copy-ItemWithRobocopy { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $DestinationPath ) $robocopyExecutable = Get-Command -Name "Robocopy.exe" -ErrorAction Stop $robocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' $robocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' if ([System.Version]$robocopyExecutable.FileVersionInfo.ProductVersion -ge [System.Version]'6.3.9600.16384') { Write-Verbose -Message $script:localizedData.RobocopyUsingUnbufferedIo $robocopyArgumentUseUnbufferedIO = '/J' } else { Write-Verbose -Message $script:localizedData.RobocopyNotUsingUnbufferedIo } $robocopyArgumentList = '{0} {1} {2} {3} {4} {5}' -f $Path, $DestinationPath, $robocopyArgumentCopySubDirectoriesIncludingEmpty, $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, $robocopyArgumentUseUnbufferedIO, $robocopyArgumentSilent $robocopyStartProcessParameters = @{ FilePath = $robocopyExecutable.Name ArgumentList = $robocopyArgumentList } Write-Verbose -Message ($script:localizedData.RobocopyArguments -f $robocopyArgumentList ) $robocopyProcess = Start-Process @robocopyStartProcessParameters -Wait -NoNewWindow -PassThru switch ($($robocopyProcess.ExitCode)) { {$_ -in 8, 16} { $errorMessage = $script:localizedData.RobocopyErrorCopying -f $_ New-InvalidOperationException -Message $errorMessage } {$_ -gt 7 } { $errorMessage = $script:localizedData.RobocopyFailuresCopying -f $_ New-InvalidResultException -Message $errorMessage } 1 { Write-Verbose -Message $script:localizedData.RobocopySuccessful } 2 { Write-Verbose -Message $script:localizedData.RobocopyRemovedExtraFilesAtDestination } 3 { Write-Verbose -Message $script:localizedData.RobocopySuccessfulAndRemovedExtraFilesAtDestination } {$_ -eq 0 -or $null -eq $_ } { Write-Verbose -Message $script:localizedData.RobocopyAllFilesPresent } } } <# .SYNOPSIS Returns the path of the current user's temporary folder. #> function Get-TemporaryFolder { [CmdletBinding()] [OutputType([System.String])] param() return [IO.Path]::GetTempPath() } <# .SYNOPSIS Returns the decimal representation of an IP Addresses .PARAMETER IPAddress The IP Address to be converted #> function ConvertTo-Decimal { [CmdletBinding()] [OutputType([System.UInt32])] param( [Parameter(Mandatory = $true)] [System.Net.IPAddress] $IPAddress ) $i = 3 $DecimalIP = 0 $IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256,$i) $i-- } return [UInt32]$DecimalIP } <# .SYNOPSIS Determines whether an IP Address is valid for a given network / subnet .PARAMETER IPAddress IP Address to be checked .PARAMETER NetworkID IP Address of the network identifier .PARAMETER SubnetMask Subnet mask of the network to be checked #> function Test-IPAddress { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Net.IPAddress] $IPAddress, [Parameter(Mandatory = $true)] [System.Net.IPAddress] $NetworkID, [Parameter(Mandatory = $true)] [System.Net.IPAddress] $SubnetMask ) # Convert all values to decimal $IPAddressDecimal = ConvertTo-Decimal -IPAddress $IPAddress $NetworkDecimal = ConvertTo-Decimal -IPAddress $NetworkID $SubnetDecimal = ConvertTo-Decimal -IPAddress $SubnetMask # Determine whether the IP Address is valid for this network / subnet return (($IPAddressDecimal -band $SubnetDecimal) -eq ($NetworkDecimal -band $SubnetDecimal)) } <# .SYNOPSIS Builds service account parameters for setup .PARAMETER ServiceAccount Credential for the service account .PARAMETER ServiceType Type of service account #> function Get-ServiceAccountParameters { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [PSCredential] $ServiceAccount, [Parameter(Mandatory = $true)] [ValidateSet('SQL','AGT','IS','RS','AS','FT')] [String] $ServiceType ) $parameters = @{} switch -Regex ($ServiceAccount.UserName.ToUpper()) { '^(?:NT ?AUTHORITY\\)?(SYSTEM|LOCALSERVICE|LOCAL SERVICE|NETWORKSERVICE|NETWORK SERVICE)$' { $parameters = @{ "$($ServiceType)SVCACCOUNT" = "NT AUTHORITY\$($Matches[1])" } } '^(?:NT SERVICE\\)(.*)$' { $parameters = @{ "$($ServiceType)SVCACCOUNT" = "NT SERVICE\$($Matches[1])" } } '.*\$' { $parameters = @{ "$($ServiceType)SVCACCOUNT" = $ServiceAccount.UserName } } default { $parameters = @{ "$($ServiceType)SVCACCOUNT" = $ServiceAccount.UserName "$($ServiceType)SVCPASSWORD" = $ServiceAccount.GetNetworkCredential().Password } } } return $parameters } <# .SYNOPSIS Starts the SQL setup process- .PARAMETER FilePath String containing the path to setup.exe. .PARAMETER ArgumentList The arguments that should be passed to setup.exe. .PARAMETER Timeout The timeout in seconds to wait for the process to finish. #> function Start-SqlSetupProcess { param ( [Parameter(Mandatory = $true)] [System.String] $FilePath, [Parameter()] [System.String] $ArgumentList, [Parameter(Mandatory = $true)] [System.UInt32] $Timeout ) $startProcessParameters = @{ FilePath = $FilePath ArgumentList = $ArgumentList } $sqlSetupProcess = Start-Process @startProcessParameters -PassThru -NoNewWindow -ErrorAction Stop Write-Verbose -Message ($script:localizedData.StartSetupProcess -f $sqlSetupProcess.Id, $startProcessParameters.FilePath, $Timeout) Wait-Process -InputObject $sqlSetupProcess -Timeout $Timeout -ErrorAction Stop return $sqlSetupProcess.ExitCode } Export-ModuleMember -Function *-TargetResource |