diff --git a/muranorepository/Services/agent_templates/AskDnsIp.template b/muranorepository/Services/agent_templates/AskDnsIp.template index e69de29..6d6bd40 100644 --- a/muranorepository/Services/agent_templates/AskDnsIp.template +++ b/muranorepository/Services/agent_templates/AskDnsIp.template @@ -0,0 +1,12 @@ +{ + "Scripts": [ + "Get-DnsListeningIpAddress.ps1" + ], + "Commands": [ + { + "Name": "Get-DnsListeningIpAddress", + "Arguments": {} + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/CreatePrimaryDC.template b/muranorepository/Services/agent_templates/CreatePrimaryDC.template index e69de29..92151e9 100644 --- a/muranorepository/Services/agent_templates/CreatePrimaryDC.template +++ b/muranorepository/Services/agent_templates/CreatePrimaryDC.template @@ -0,0 +1,16 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Install-RolePrimaryDomainController.ps1" + ], + "Commands": [ + { + "Name": "Install-RolePrimaryDomainController", + "Arguments": { + "DomainName": "$domain", + "SafeModePassword": "$recoveryPassword" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/CreateSecondaryDC.template b/muranorepository/Services/agent_templates/CreateSecondaryDC.template index e69de29..6b492f2 100644 --- a/muranorepository/Services/agent_templates/CreateSecondaryDC.template +++ b/muranorepository/Services/agent_templates/CreateSecondaryDC.template @@ -0,0 +1,18 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Install-RoleSecondaryDomainController.ps1" + ], + "Commands": [ + { + "Name": "Install-RoleSecondaryDomainController", + "Arguments": { + "DomainName": "$domain", + "UserName": "Administrator", + "Password": "$domainPassword", + "SafeModePassword": "$recoveryPassword" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/DeployWebApp.template b/muranorepository/Services/agent_templates/DeployWebApp.template index e69de29..16ead07 100644 --- a/muranorepository/Services/agent_templates/DeployWebApp.template +++ b/muranorepository/Services/agent_templates/DeployWebApp.template @@ -0,0 +1,15 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "DeployWebApp.ps1" + ], + "Commands": [ + { + "Name": "Deploy-WebAppFromGit", + "Arguments": { + "URL": "$repository" + } + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/InstallIIS.template b/muranorepository/Services/agent_templates/InstallIIS.template index e69de29..6389f3b 100644 --- a/muranorepository/Services/agent_templates/InstallIIS.template +++ b/muranorepository/Services/agent_templates/InstallIIS.template @@ -0,0 +1,22 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "CopyPrerequisites.ps1", + "InstallIIS.ps1" + ], + "Commands": [ + { + "Name": "Copy-Prerequisites", + "Arguments": { + "Destination": "C:\\Prerequisites" + } + }, + { + "Name": "Install-WebServer", + "Arguments": { + "PrerequisitesPath": "C:\\Prerequisites" + } + } + ], + "RebootOnCompletion": 0 +} diff --git a/muranorepository/Services/agent_templates/InstallMsSqlServer.template b/muranorepository/Services/agent_templates/InstallMsSqlServer.template index e69de29..d1eaacc 100644 --- a/muranorepository/Services/agent_templates/InstallMsSqlServer.template +++ b/muranorepository/Services/agent_templates/InstallMsSqlServer.template @@ -0,0 +1,24 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "OptionParser.ps1", + "SQLServerOptionParsers.ps1", + "SQLServerInstaller.ps1", + "Install-SQLServer.ps1", + "Alter-FirewallRulesForSQL.ps1" + ], + "Commands": [ + { + "Name": "Install-SQLServer", + "Arguments": { + "SAPassword": "$saPassword", + "MixedModeAuth": "$mixedModeAuth" + } + }, + { + "Name": "Enable-SQLExternalAccess", + "Arguments": {} + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/JoinDomain.template b/muranorepository/Services/agent_templates/JoinDomain.template index e69de29..3d8cbef 100644 --- a/muranorepository/Services/agent_templates/JoinDomain.template +++ b/muranorepository/Services/agent_templates/JoinDomain.template @@ -0,0 +1,25 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Join-Domain.ps1" + ], + "Commands": [ + { + "Name": "Set-NetworkAdapterConfiguration", + "Arguments": { + "FirstAvailable": true, + "DNSServer": "$dnsIp" + } + }, + { + "Name": "Join-Domain", + "Arguments": { + "Username": "$domainUser", + "Password": "$domainPassword", + "DomainName": "$domain", + "OUPath": "$ouPath" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/LeaveDomain.template b/muranorepository/Services/agent_templates/LeaveDomain.template index e69de29..262ac0b 100644 --- a/muranorepository/Services/agent_templates/LeaveDomain.template +++ b/muranorepository/Services/agent_templates/LeaveDomain.template @@ -0,0 +1,5 @@ +{ + "Scripts": [], + "Commands": [], + "RebootOnCompletion": 0, +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SetPassword.template b/muranorepository/Services/agent_templates/SetPassword.template index e69de29..101db0e 100644 --- a/muranorepository/Services/agent_templates/SetPassword.template +++ b/muranorepository/Services/agent_templates/SetPassword.template @@ -0,0 +1,17 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Set-LocalUserPassword.ps1" + ], + "Commands": [ + { + "Name": "Set-LocalUserPassword", + "Arguments": { + "UserName": "Administrator", + "Password": "$adminPassword", + "Force": true + } + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/ConfigureEnvironmentForAOAG.template b/muranorepository/Services/agent_templates/SqlServerCluster/ConfigureEnvironmentForAOAG.template index e69de29..df12ddf 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/ConfigureEnvironmentForAOAG.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/ConfigureEnvironmentForAOAG.template @@ -0,0 +1,18 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "SQLServerForAOAG.ps1" + ], + "Commands": [ + { + "Name": "Enable-TrustedHosts", + "Arguments": {} + }, + { + "Name": "New-SharedFolderForAOAG", + "Arguments": { + "PrimaryNode": "$primaryNode" + } + } + ] +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/FailoverCluster.template b/muranorepository/Services/agent_templates/SqlServerCluster/FailoverCluster.template index e69de29..cf13598 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/FailoverCluster.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/FailoverCluster.template @@ -0,0 +1,21 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Start-PowerShellProcess.ps1", + "Failover-Cluster.ps1" + ], + "Commands": [ + { + "Name": "New-FailoverCluster", + "Arguments": { + "UserName": "$domainAdminAccountName", + "ClusterNodes": "$clusterNodes", + "DomainName": "$domainName", + "ClusterName": "$clusterName", + "UserPassword": "$domainAdminAccountPassword", + "StaticAddress": "$clusterIP" + } + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/FailoverClusterPrerequisites.template b/muranorepository/Services/agent_templates/SqlServerCluster/FailoverClusterPrerequisites.template index e69de29..e439f54 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/FailoverClusterPrerequisites.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/FailoverClusterPrerequisites.template @@ -0,0 +1,28 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Update-ServiceConfig.ps1", + "SQLServerForAOAG.ps1", + "Failover-Cluster.ps1" + ], + "Commands": [ + { + "Name": "Install-FailoverClusterPrerequisites", + "Arguments": {} + }, + { + "Name": "Enable-TrustedHosts", + "Arguments": {} + }, + { + "Name": "Update-ServiceConfig", + "Arguments": { + "Password": "$domainAdminAccountPassword", + "Name": "Murano Agent", + "RunAsUser": "$domainAdminAccountName", + "DomainName": "$domainName" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGPrimaryReplica.template b/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGPrimaryReplica.template index e69de29..877a9f7 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGPrimaryReplica.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGPrimaryReplica.template @@ -0,0 +1,28 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "OptionParser.ps1", + "SQLServerOptionParsers.ps1", + "SQLServerInstaller.ps1", + "Export-Function.ps1", + "Start-PowerShellProcess.ps1", + "SQLServerForAOAG.ps1" + ], + "Commands": [ + { + "Name": "Initialize-AOAGPrimaryReplica", + "Arguments": { + "UserName": "$domainAdminAccountName", + "NodeList": "$nodeList", + "PrimaryNode": "$primaryNode", + "DomainName": "$domainName", + "UserPassword": "$domainAdminAccountPassword", + "GroupName": "$groupName", + "ListenerName": "$listenerName", + "SyncModeNodeList": "$syncModeNodeList", + "ListenerIP": "$listenerIP", + "DatabaseList": "$databaseList" + } + } + ] +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGSecondaryReplica.template b/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGSecondaryReplica.template index e69de29..604dae6 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGSecondaryReplica.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAOAGSecondaryReplica.template @@ -0,0 +1,23 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "OptionParser.ps1", + "SQLServerOptionParsers.ps1", + "SQLServerInstaller.ps1", + "Export-Function.ps1", + "Start-PowerShellProcess.ps1", + "SQLServerForAOAG.ps1" + ], + "Commands": [ + { + "Name": "Initialize-AOAGSecondaryReplica", + "Arguments": { + "UserName": "$domainAdminAccountName", + "UserPassword": "$domainAdminAccountPassword", + "NodeList": "$nodeList", + "PrimaryNode": "$primaryNode", + "DomainName": "$domainName" + } + } + ] +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAlwaysOn.template b/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAlwaysOn.template index e69de29..01fdb49 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAlwaysOn.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/InitializeAlwaysOn.template @@ -0,0 +1,23 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "OptionParser.ps1", + "SQLServerOptionParsers.ps1", + "SQLServerInstaller.ps1", + "Export-Function.ps1", + "Start-PowerShellProcess.ps1", + "SQLServerForAOAG.ps1" + ], + "Commands": [ + { + "Name": "Initialize-AlwaysOnAvailabilityGroup", + "Arguments": { + "DomainAdminAccountName": "$domainAdminAccountName", + "DomainAdminAccountPassword": "$domainAdminAccountPassword", + "NodeList": "$nodeList", + "PrimaryNode": "$primaryNode", + "DomainName": "$domainName" + } + } + ] +} \ No newline at end of file diff --git a/muranorepository/Services/agent_templates/SqlServerCluster/InstallSqlServerForAOAG.template b/muranorepository/Services/agent_templates/SqlServerCluster/InstallSqlServerForAOAG.template index e69de29..658c50d 100644 --- a/muranorepository/Services/agent_templates/SqlServerCluster/InstallSqlServerForAOAG.template +++ b/muranorepository/Services/agent_templates/SqlServerCluster/InstallSqlServerForAOAG.template @@ -0,0 +1,27 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "OptionParser.ps1", + "SQLServerOptionParsers.ps1", + "SQLServerInstaller.ps1", + "SQLServerForAOAG.ps1" + ], + "Commands": [ + { + "Name": "Disable-Firewall", + "Arguments": {} + }, + { + "Name": "Install-SQLServerForAOAG", + "Arguments": { + "SQLServiceUserPassword": "$domainAdminAccountPassword", + "SQLServiceUserDomain": "$domainName", + "SQLServiceUserName": "$domainAdminAccountName" + } + }, + { + "Name": "Install-SqlServerPowerShellModule", + "Arguments": {} + } + ] +} \ No newline at end of file diff --git a/muranorepository/Services/scripts/Alter-FirewallRulesForSQL.ps1 b/muranorepository/Services/scripts/Alter-FirewallRulesForSQL.ps1 index e69de29..2e2594f 100644 --- a/muranorepository/Services/scripts/Alter-FirewallRulesForSQL.ps1 +++ b/muranorepository/Services/scripts/Alter-FirewallRulesForSQL.ps1 @@ -0,0 +1,61 @@ +trap { + &$TrapHandler +} + + +$FW_Rules = @{ + "SQL Server Data Connection" = "1433"; + "SQL Admin Connection" = "1434"; + "SQL Service Broker" = "4022"; + "SQL Debugger/RPC"="135"; +} + + +$FW_Proto = "TCP" + + +function Add-NetshFirewallRule { + param ( + [HashTable] $hshRules, + [String] $proto + ) + + + foreach ($h in $hshRules.GetEnumerator()) { + try { + $command="advfirewall firewall add rule name=`"$($h.Name)`" dir=in action=allow protocol=$proto localport=$($h.Value)" + Start-Process -FilePath netsh -ArgumentList $command -Wait + } + catch { + $except= $_ | Out-String + Write-LogError "Add rule $($h.Name) FAILS with $except" + } + } +} + +function Remove-NetShFirewallRule { + param ( + [HashTable] $hshRules + ) + + foreach ($h in $hshRules.GetEnumerator()) { + try { + $command="advfirewall firewall delete rule name=`"$($h.Name)`"" + Start-Process -FilePath netsh -ArgumentList $command -Wait + } + catch { + $except= $_ | Out-String + Write-LogError "Delete rule $($h.Name) FAILS with $except" + } + } +} + + +function Enable-SQLExternalAccess { + Add-NetshFirewallRule $FW_Rules $FW_Proto +} + + +function Disable-SQLExternalAccess { + Remove-NetshFirewallRule $FW_Rules $FW_Proto +} diff --git a/muranorepository/Services/scripts/CopyPrerequisites.ps1 b/muranorepository/Services/scripts/CopyPrerequisites.ps1 index e69de29..5e939c4 100644 --- a/muranorepository/Services/scripts/CopyPrerequisites.ps1 +++ b/muranorepository/Services/scripts/CopyPrerequisites.ps1 @@ -0,0 +1,50 @@ + +trap { + &$TrapHandler +} + + +Function Copy-Prerequisites { + param ( + [String] $Path = '', + [String] $Destination = '' + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Write-Log "--> Copy-Prerequisites" + + if ($Destination -eq '') { + throw("Copy-Prerequisites: Destination path not specified!") + } + + if ($Path -eq '') { + $Path = [Environment]::GetEnvironmentVariable('MuranoFileShare') + if ($Path -eq $null) { + throw("Copy-Prerequisites: Unable to determine source path for prerequisites.") + } + } + + Write-Log "Creating new PSDrive ..." + New-PSDrive -Name 'P' -PSProvider 'FileSystem' -Root $Path | Out-Null + + Write-Log "Creating destination folder ..." + New-Item -Path $Destination -ItemType Container -Force | Out-Null + + Write-Log "Copying items ..." + Copy-Item -Path 'P:\Prerequisites\IIS' -Destination $Destination -Recurse -Force | Out-Null + + Write-Log "Removing PSDrive ..." + Remove-PSDrive -Name 'P' -PSProvider 'FileSystem' -Force | Out-Null + + Write-Log "<-- Copy-Prerequisites" + } +} diff --git a/muranorepository/Services/scripts/DeployWebApp.ps1 b/muranorepository/Services/scripts/DeployWebApp.ps1 index e69de29..5dd90f4 100644 --- a/muranorepository/Services/scripts/DeployWebApp.ps1 +++ b/muranorepository/Services/scripts/DeployWebApp.ps1 @@ -0,0 +1,152 @@ + +trap { + &$TrapHandler +} + + +Function Register-WebApp { +<# +.LINKS + +http://www.iis.net/learn/manage/powershell/powershell-snap-in-creating-web-sites-web-applications-virtual-directories-and-application-pools +#> + param ( + [String] $Source, + [String] $Path = "C:\inetpub\wwwroot", + [String] $Name = "", + [String] $Username = "", + [String] $Password = "" + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Import-Module WebAdministration + + if ($Name -eq "") { + $Name = @([IO.Path]::GetDirectoryName($Source) -split '\\')[-1] + if ($Name -eq "wwwroot") { + throw("Application pool name couldn't be 'wwwroot'.") + } + } + else { + $Path = [IO.Path]::Combine($Path, $Name) + } + + $null = Copy-Item -Path $Source -Destination $Path -Recurse -Force + + # Create new application pool + $AppPool = New-WebAppPool -Name $Name -Force + #$AppPool = Get-Item "IIS:\AppPools\$Name" + $AppPool.managedRuntimeVersion = 'v4.0' + $AppPool.managedPipelineMode = 'Classic' + $AppPool.processModel.loadUserProfile = $true + $AppPool.processModel.logonType = 'LogonBatch' + + #Set Identity type + if ($Username -eq "") { + $AppPool.processModel.identityType = 'ApplicationPoolIdentity' + } + else { + $AppPool.processModel.identityType = 'SpecificUser' + $AppPool.processModel.userName = $Username + $AppPool.processModel.password = $Password + $null = $AppPool | Set-Item + } + + + # Create Website + $WebSite = New-WebSite -Name $Name -Port 80 -HostHeader $Name -PhysicalPath $Path -Force + #$WebSite = Get-Item "IIS:\Sites\$Name" + + # Set the Application Pool + $null = Set-ItemProperty "IIS:\Sites\$Name" 'ApplicationPool' $Name + + #Turn on Directory Browsing + #Set-WebConfigurationProperty -Filter '/system.webServer/directoryBrowse' -Name 'enabled' -Value $true -PSPath "IIS:\Sites\$Name" + + # Update Authentication + #Set-WebConfigurationProperty -Filter '/system.WebServer/security/authentication/AnonymousAuthentication' -Name 'enabled' -Value $true -Location $name + #Set-WebConfigurationProperty -Filter '/system.WebServer/security/authentication/windowsAuthentication' -Name 'enabled' -Value $false -Location $Name + #Set-WebConfigurationProperty -Filter '/system.WebServer/security/authentication/basicAuthentication' -Name 'enabled' -Value $false -Location $Name + + $null = $WebSite.Start() + + $null = Add-Content -Path "C:\Windows\System32\Drivers\etc\hosts" -Value "127.0.0.1 $Name" + + # Remove standard IIS 'Hello World' application from localhost:80 + $null = Get-WebBinding 'Default Web Site' | Remove-WebBinding + # Add new application on http://localhost:80 + $null = New-WebBinding -Name "$Name" -IP "*" -Port 80 -Protocol http + } +} + + + +Function Deploy-WebAppFromGit { + param ( + [String] $URL, + [String] $TempPath = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()), + [String] $OutputPath = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()) + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Write-Log "TempPath = '$TempPath'" + Write-Log "OutputPath = '$OutputPath'" + + + # Fetch web application + #---------------------- + Write-Log "Fetching sources from Git ..." + + $null = New-Item -Path $TempPath -ItemType Container + $null = Exec -FilePath 'git.exe' -ArgumentList @('clone', $URL) -WorkingDir $TempPath -RedirectStreams + + $Path = @(Get-ChildItem $TempPath)[0].FullName + #---------------------- + + + # Build web application + #---------------------- + Write-Log "Building sources ..." + + $msbuild = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe" + + $null = New-Item -Path $OutputPath -ItemType Container + + $SlnFiles = @(Get-ChildItem -Path $Path -Filter *.sln -Recurse) + + # Start new processs with additional env variables: + #* VisualStudioVersion = "10.0" + #* EnableNuGetPackageRestore = "true" + $null = Exec -FilePath $msbuild ` + -ArgumentList @($SlnFiles[0].FullName, "/p:OutputPath=$OutputPath") ` + -Environment @{'VisualStudioVersion' = '10.0'; 'EnableNuGetPackageRestore' = 'true'} ` + -RedirectStreams + + $AppFolder = @(Get-ChildItem ([IO.Path]::Combine($OutputPath, '_PublishedWebsites')))[0] + #---------------------- + + + # Install web application + #------------------------ + $null = Register-WebApp -Source $AppFolder.FullName -Name $AppFolder.Name + #------------------------ + } +} diff --git a/muranorepository/Services/scripts/Export-Function.ps1 b/muranorepository/Services/scripts/Export-Function.ps1 index e69de29..8cd1b57 100644 --- a/muranorepository/Services/scripts/Export-Function.ps1 +++ b/muranorepository/Services/scripts/Export-Function.ps1 @@ -0,0 +1,61 @@ + +trap { + &$TrapHandler +} + +function Export-Function { + param ( + [String[]] $Name, + + [Parameter(ValueFromPipeline=$true)] + [String] $Path = [IO.Path]::GetTempFileName(), + + [Switch] $All + ) + + if ([IO.Path]::GetExtension($Path) -ne 'ps1') { + $null = Rename-Item -Path $Path -NewName "$Path.ps1" -Force + $Path = "$Path.ps1" + } + + $SystemFunctions = @( + 'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:', + 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:', + 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', + 'cd..', 'cd\', 'help', 'mkdir', 'more', 'oss', 'prompt', + 'Clear-Host', 'Get-Verb', 'Pause', 'TabExpansion2' + ) + + if ($All) { + Get-ChildItem Function: | + Where-Object {$_.ModuleName -eq ''} | + Where-Object {$SystemFunctions -notcontains $_.Name} | + ForEach-Object { + Add-Content -Path $Path -Value @" + + +function $($_.Name) { +$($_.ScriptBlock) +} + +"@ + } + } + else { + foreach ($FunctionName in $Name) { + $FunctionObject = Get-ChildItem "Function:\$FunctionName" + if ($FunctionObject -ne $null) { + Add-Content -Path $Path -Value @" + + +function $FunctionName { +$($FunctionObject.ScriptBlock) +} + +"@ + } + } + } + + return $Path +} diff --git a/muranorepository/Services/scripts/Failover-Cluster.ps1 b/muranorepository/Services/scripts/Failover-Cluster.ps1 index e69de29..e444ac2 100644 --- a/muranorepository/Services/scripts/Failover-Cluster.ps1 +++ b/muranorepository/Services/scripts/Failover-Cluster.ps1 @@ -0,0 +1,239 @@ +<# +.DESCRIPTION + +## Failover Cluster Input Data (from the UI) + +* Domain Membership + - [String] / [Select box] $DomainName - Domain name +* Domain User Credentials + - [String] $UserName - Username + - [Password string] $UserPassword - User password +* Shared Folder Information + - [String] $ShareServer - Server which will host the folder + - [String] $ShareName - Share name + - [String] $SharePath - Shared folder internal path +* Failover Cluster Members + - [String] $ClusterName - Cluster name + - [String] $ClusterIP - Static IP address that will be assigned to the cluster + - [String[]] $ClusterNodes - List of node names + + + +## Failover Cluster creation workflow + +* Create AD domain +* Join all the VMs to that domain +* Prepare nodes + - Install Failover Cluster prerequisites on all FC nodes +* Create failover cluster + - Create new cluster + - Add members +* Confugure FC quorum + - Create new folder that will be shared + - Share that folder with appropriate permissions + - Configure quorum mode + + + +## Helpful SmbShare* Functions + +* New-SmbShare +* Grant-SmbShareAccess + +#> + +trap { + &$TrapHandler +} + + + +function Install-FailoverClusterPrerequisites { + #Import-Module FailoverClusters + + #Add-WindowsFeature Failover-Clustering, RSAT-Clustering-PowerShell +} + + + +function New-FailoverClusterSharedFolder { + param ( + [String] $ClusterName, + [String] $DomainName, + [String] $ShareServer, + [String] $SharePath = $($Env:SystemDrive + '\FCShare'), + [String] $ShareName = 'FCShare', + [String] $UserName, + [String] $UserPassword, + $Credential = $null + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Write-Log "--> New-FailoverClusterSharedFolder" + + Write-Log "Creating shared folder for Failover Cluster ..." + + if ($Credential -eq $null) { + $Credential = New-Credential -UserName "$DomainName\$UserName" -Password "$UserPassword" + } + + if ((Test-Connection -ComputerName $ShareServer -Count 1 -Quiet) -eq $false) { + throw("Server '$ShareServer' is unreachable via ICMP.") + } + + $Session = New-PSSession -ComputerName $ShareServer -Credential $Credential + + Write-Log "Creating folder on '$ShareServer' ..." + Invoke-Command -Session $Session -ScriptBlock { + param ( + [String] $SharePath, + [String] $ShareName, + [String] $ClusterAccount + ) + + Remove-SmbShare -Name $ShareName -Force -ErrorAction 'SilentlyContinue' + Remove-Item -Path $SharePath -Force -ErrorAction 'SilentlyContinue' + + New-Item -Path $SharePath -ItemType Container -Force + + New-SmbShare -Path $SharePath ` + -Name $ShareName ` + -FullAccess "$ClusterAccount", 'Everyone' ` + -Description "Shared folder for Failover Cluster." + + } -ArgumentList $SharePath, $ShareName, "$DomainName\$ClusterName`$" + + Write-Log "Confguring Failover Cluster to use shared folder as qourum resourse ..." + + $null = Set-ClusterQuorum -NodeAndFileShareMajority "\\$ShareServer\$ShareName" + + Write-Log "<-- New-FailoverClusterSharedFolder" + } +} + + + +function New-FailoverCluster { + param ( + [String] $ClusterName, + [String] $StaticAddress, + [String[]] $ClusterNodes, + [String] $DomainName, + [String] $UserName, + [String] $UserPassword, + $Credential + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Write-Log "ClusterNodes: $($ClusterNodes -join ', ')" + + if ($Credential -eq $null) { + $Credential = New-Credential -UserName "$DomainName\$UserName" -Password "$UserPassword" + } + + foreach ($Node in $ClusterNodes) { + Write-LogDebug "Installing Failover Cluster modules on '$Node' ..." + $null = Invoke-Command ` + -ComputerName $Node ` + -Credential $Credential ` + -ScriptBlock { + Add-WindowsFeature Failover-Clustering, RSAT-Clustering-PowerShell + } + } + + Import-Module FailoverClusters + + if ((Get-Cluster $ClusterName -ErrorAction SilentlyContinue) -eq $null) { + Write-Log "Creating new cluster '$ClusterName' ..." +<# + Start-PowerShellProcess -Command @" +Import-Module FailoverClusters +New-Cluster -Name '$ClusterName' -StaticAddress '$StaticAddress' +"@ -Credential $Credential -NoBase64 +#> + New-Cluster -Name "$ClusterName" -StaticAddress "$StaticAddress" + Start-Sleep -Seconds 30 + } + else { + Write-Log "Cluster '$ClusterName' already exists." + } + + foreach ($Node in $ClusterNodes) { + Write-Log "Adding node '$Node' to the cluster '$ClusterName' ..." + if ((Get-ClusterNode $Node -ErrorAction SilentlyContinue) -eq $null) { + Write-Log "Adding node ..." +<# + Start-PowerShellProcess -Command @" +Import-Module FailoverClusters +Add-ClusterNode -Cluster '$ClusterName' -Name '$Node' +"@ -Credential $Credential -NoBase64 +#> + Add-ClusterNode -Cluster "$ClusterName" -Name "$Node" + } + else { + Write-Log "Node '$Node' already a part of the cluster '$ClusterName'." + } + } + } +} + + + +<# + +# Example + +$DomainName = 'fc-acme.local' +$DomainUser = 'Administrator' +$DomainPassword = 'P@ssw0rd' + +$ClusterName = 'fc-test' +$ClusterIP = '10.200.0.60' +$ClusterNodes = @('fc-node-01','fc-node-02','fc-node-03') + +$ShareServer = 'fc-dc-01' +$ShareName = 'FCShare' + +$SharePath = "C:\$ShareName" + + + +Import-Module CoreFunctions -Force + +$Creds = New-Credential ` + -UserName "$DomainName\$DomainUser" ` + -Password "$DomainPassword" + +New-FailoverCluster ` + -ClusterName $ClusterName ` + -StaticAddress $ClusterIP ` + -ClusterNodes $ClusterNodes ` + -Credential $Creds + +New-FailoverClusterSharedFolder ` + -ClusterName $ClusterName ` + -DomainName $DomainName ` + -ShareServer $ShareServer ` + -SharePath "$SharePath" ` + -ShareName "$ShareName" ` + -Credential $Creds + +#> diff --git a/muranorepository/Services/scripts/Get-DnsListeningIpAddress.ps1 b/muranorepository/Services/scripts/Get-DnsListeningIpAddress.ps1 index e69de29..1db0b85 100644 --- a/muranorepository/Services/scripts/Get-DnsListeningIpAddress.ps1 +++ b/muranorepository/Services/scripts/Get-DnsListeningIpAddress.ps1 @@ -0,0 +1,7 @@ + +function Get-DnsListeningIpAddress { + Import-Module DnsServer + + (Get-DNSServer -ComputerName localhost).ServerSetting.ListeningIpAddress | + Where-Object { $_ -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" } +} diff --git a/muranorepository/Services/scripts/ImportCoreFunctions.ps1 b/muranorepository/Services/scripts/ImportCoreFunctions.ps1 index e69de29..85e6434 100644 --- a/muranorepository/Services/scripts/ImportCoreFunctions.ps1 +++ b/muranorepository/Services/scripts/ImportCoreFunctions.ps1 @@ -0,0 +1,68 @@ + +Import-Module CoreFunctions -Force +Initialize-Logger 'MuranoAgent' 'C:\Murano\PowerShell.log' + + +function Show-InvocationInfo { + param ( + $Invocation, + [Switch] $End + ) + + if ($End) { + Write-LogDebug "" + } + else { + Write-LogDebug "" + Write-LogDebug "" + foreach ($Parameter in $Invocation.MyCommand.Parameters) { + foreach ($Key in $Parameter.Keys) { + $Type = $Parameter[$Key].ParameterType.FullName + foreach ($Value in $Invocation.BoundParameters[$Key]) { + Write-LogDebug "[$Type] $Key = '$Value'" + } + } + } + Write-LogDebug "" + } +} + + +$TrapHandler = { + Write-LogError "" + Write-LogError $_ -EntireObject + Write-LogError "" + break +} + + +trap { + &$TrapHandler +} + +$ErrorActionPreference = 'Stop' + + +<# +# Usage example for Show-InvocationInfo + +function MyFunction { + param ( + [String] $Value1, + [String] $Value2, + [Int] $Int1 + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + # Main code here + } +} +#> diff --git a/muranorepository/Services/scripts/Install-RolePrimaryDomainController.ps1 b/muranorepository/Services/scripts/Install-RolePrimaryDomainController.ps1 index e69de29..e8f1e5a 100644 --- a/muranorepository/Services/scripts/Install-RolePrimaryDomainController.ps1 +++ b/muranorepository/Services/scripts/Install-RolePrimaryDomainController.ps1 @@ -0,0 +1,43 @@ + +trap { + &$TrapHandler +} + + +Function Install-RolePrimaryDomainController { + param ( + [String] $DomainName, + [String] $SafeModePassword + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Add-WindowsFeatureWrapper ` + -Name "DNS","AD-Domain-Services","RSAT-DFS-Mgmt-Con" ` + -IncludeManagementTools ` + -NotifyRestart + + Write-Log "Creating first domain controller ..." + + $SMAP = ConvertTo-SecureString -String $SafeModePassword -AsPlainText -Force + + $null = Install-ADDSForest ` + -DomainName $DomainName ` + -SafeModeAdministratorPassword $SMAP ` + -DomainMode Default ` + -ForestMode Default ` + -NoRebootOnCompletion ` + -Force + + Write-Log "Waiting 60 seconds for reboot ..." + Start-Sleep -Seconds 60 + } +} diff --git a/muranorepository/Services/scripts/Install-RoleSecondaryDomainController.ps1 b/muranorepository/Services/scripts/Install-RoleSecondaryDomainController.ps1 index e69de29..be9258e 100644 --- a/muranorepository/Services/scripts/Install-RoleSecondaryDomainController.ps1 +++ b/muranorepository/Services/scripts/Install-RoleSecondaryDomainController.ps1 @@ -0,0 +1,69 @@ + +trap { + &$TrapHandler +} + + +Function Install-RoleSecondaryDomainController +{ +<# +.SYNOPSIS +Install additional (secondary) domain controller. + +#> + param + ( + [String] + # Domain name to join to. + $DomainName, + + [String] + # Domain user who is allowed to join computer to domain. + $UserName, + + [String] + # User's password. + $Password, + + [String] + # Domain controller recovery mode password. + $SafeModePassword + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + $Credential = New-Credential -UserName "$DomainName\$UserName" -Password $Password + + # Add required windows features + Add-WindowsFeatureWrapper ` + -Name "DNS","AD-Domain-Services","RSAT-DFS-Mgmt-Con" ` + -IncludeManagementTools ` + -NotifyRestart + + + Write-Log "Adding secondary domain controller ..." + + $SMAP = ConvertTo-SecureString -String $SafeModePassword -AsPlainText -Force + + Install-ADDSDomainController ` + -DomainName $DomainName ` + -SafeModeAdministratorPassword $SMAP ` + -Credential $Credential ` + -NoRebootOnCompletion ` + -Force ` + -ErrorAction Stop | Out-Null + + Write-Log "Waiting for restart ..." + # Stop-Execution -ExitCode 3010 -ExitString "Computer must be restarted to finish domain controller promotion." + # Write-Log "Restarting computer ..." + # Restart-Computer -Force + } +} diff --git a/muranorepository/Services/scripts/Install-SQLServer.ps1 b/muranorepository/Services/scripts/Install-SQLServer.ps1 index e69de29..61fb566 100644 --- a/muranorepository/Services/scripts/Install-SQLServer.ps1 +++ b/muranorepository/Services/scripts/Install-SQLServer.ps1 @@ -0,0 +1,84 @@ + +trap { + &$TrapHandler +} + + + +Function ConvertTo-Boolean { + param ( + $InputObject, + [Boolean] $Default = $false + ) + try { + [System.Convert]::ToBoolean($InputObject) + } + catch { + $Default + } +} + + + +Function Show-Environment { + foreach ($item in (Get-ChildItem Env:)) { + Write-Log ("'{0}' --> '{1}'" -f $item.Name, $item.Value) + } +} + + + +Function Install-SqlServer { + param ( + [String] $SetupRoot = '', + [String] $SAPassword = '', + [String] $MuranoFileShare = '', + [Switch] $MixedModeAuth = $false, + [Switch] $UpdateEnabled = $false + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($SetupRoot -eq '') { + if ($MuranoFileShare -eq '') { + $MuranoFileShare = [Environment]::GetEnvironmentVariable('MuranoFileShare') + if ($MuranoFileShare -eq '') { + throw("Unable to find MuranoFileShare path.") + } + } + + $SetupRoot = [IO.Path]::Combine($MuranoFileShare, 'Prerequisites\SQL Server\2012') + } + + #$MixedModeAuthSwitch = ConvertTo-Boolean $MixedModeAuth + + $ExtraOptions = @{} + + if ($MixedModeAuth -eq $true) { + $ExtraOptions += @{'SECURITYMODE' = 'SQL'} + if ($SAPassword -eq '') { + throw("SAPassword must be set when MixedModeAuth is requisted!") + } + } + + if ($SAPassword -ne '') { + $ExtraOptions += @{'SAPWD' = $SAPassword} + } + + if (-not $UpdateEnabled) { + $ExtraOptions += @{'UpdateEnabled' = $false} + } + + Show-Environment + + New-SqlServer -SetupRoot $SetupRoot -ExtraOptions $ExtraOptions + } +} diff --git a/muranorepository/Services/scripts/InstallIIS.ps1 b/muranorepository/Services/scripts/InstallIIS.ps1 index e69de29..57e7dc4 100644 --- a/muranorepository/Services/scripts/InstallIIS.ps1 +++ b/muranorepository/Services/scripts/InstallIIS.ps1 @@ -0,0 +1,72 @@ + +trap { + &$TrapHandler +} + + +Function Install-WebServer { + param ( + [String] $PrerequisitesPath + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Write-Log "--> Install-WebServer" + + $FeatureList = @( + 'Web-Server', + 'Web-Net-Ext45', + 'Web-ASP', + 'Web-Asp-Net45', + 'Web-ISAPI-Ext', + 'Web-ISAPI-Filter', + 'Web-Includes' + ) + + $PrerequisitesList = @( + 'AspNetMvc4Setup.exe', + 'WebApplications.exe' + ) + + $PrerequisitesPath = [IO.Path]::Combine($PrerequisitesPath, 'IIS') + + Write-Log "Validating prerequisites based on the list ..." + foreach ($FileName in $PrerequisitesList) { + $FilePath = [IO.Path]::Combine($PrerequisitesPath, $FileName) + if (-not (Test-Path -Path $FilePath -PathType Leaf)) { + throw("Prerequisite file not found: '$FilePath'") + } + } + + Import-Module ServerManager + + Write-Log "Installing Web Server ..." + Install-WindowsFeature $FeatureList -IncludeManagementTools + + Write-Log "Installing AspNetMvp4 ..." + $Exec = Exec -FilePath $([IO.Path]::Combine($PrerequisitesPath, 'AspNetMvc4Setup.exe')) -ArgumentList '/q' -PassThru + if ($Exec.ExitCode -ne 0) { + throw("Installation of 'AspNetMvc4Setup.exe' failed. Process exit code '$($Exec.ExitCode)'") + } + + # Extract WebApplications folder with *.target files to + # C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0 + Write-Log "Installing WebApplication targets ..." + $WebApplicationsTargetsRoot = 'C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0' + $null = New-Item -Path $WebApplicationsTargetsRoot -ItemType Container + $Exec = Exec -FilePath $([IO.Path]::Combine($PrerequisitesPath, 'WebApplications.exe')) -ArgumentList @("-o`"$WebApplicationsTargetsRoot`"", '-y') -PassThru + if ($Exec.ExitCode -ne 0) { + throw("Installation of 'WebApplications.exe' failed. Process exit code '$($Exec.ExitCode)'") + } + + Write-Log "<-- Install-WebServer" + } +} diff --git a/muranorepository/Services/scripts/Join-Domain.ps1 b/muranorepository/Services/scripts/Join-Domain.ps1 index e69de29..4716709 100644 --- a/muranorepository/Services/scripts/Join-Domain.ps1 +++ b/muranorepository/Services/scripts/Join-Domain.ps1 @@ -0,0 +1,67 @@ + +trap { + &$TrapHandler +} + + +Function Join-Domain { +<# +.SYNOPSIS +Executes "Join domain" action. + +Requires 'CoreFunctions' module. +#> + param ( + [String] $DomainName = '', + [String] $UserName = '', + [String] $Password = '', + [String] $OUPath = '', + [Switch] $AllowRestart + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($UserName -eq '') { + $UserName = 'Administrator' + } + + $Credential = New-Credential -UserName "$DomainName\$UserName" -Password $Password + + + if (Test-ComputerName -DomainName $DomainName -ErrorAction 'SilentlyContinue') { + Write-LogWarning "Computer already joined to domain '$DomainName'" + } + else { + Write-Log "Joining computer to domain '$DomainName' ..." + + if ($OUPath -eq '') { + Add-Computer -DomainName $DomainName -Credential $Credential -Force + } + else { + Add-Computer -DomainName $DomainName -Credential $Credential -OUPath $OUPath -Force + } + + $null = Exec 'ipconfig' @('/registerdns') -RedirectStreams + + Write-Log "Waiting 30 seconds to restart ..." + Start-Sleep -Seconds 30 + <# + if ($AllowRestart) { + Write-Log "Restarting computer ..." + Restart-Computer -Force + } + else { + Write-Log "Please restart the computer now." + } + #> + } + } +} diff --git a/muranorepository/Services/scripts/New-SqlServerSystemAccount.ps1 b/muranorepository/Services/scripts/New-SqlServerSystemAccount.ps1 index e69de29..a3b306b 100644 --- a/muranorepository/Services/scripts/New-SqlServerSystemAccount.ps1 +++ b/muranorepository/Services/scripts/New-SqlServerSystemAccount.ps1 @@ -0,0 +1,64 @@ + +trap { + &$TrapHandler +} + + + +function New-SqlServerSystemAccount { + param ( + # (REQUIRED) Domain Name + [Parameter(Mandatory=$true)] + [String] $DomainName, + + # (REQUIRED) User name who has permissions to create and modify userPassword + # Usually this is the domain administrator '$domainName\Administrator' account + [Parameter(Mandatory=$true)] + [String] $UserName, + + # (REQUIRED) Password for that user + [Parameter(Mandatory=$true)] + [String] $UserPassword, + + # (REQUIRED) User name for a new account that will be used to run SQL Server + [Parameter(Mandatory=$true)] + [String] $SQLServiceUserName, + + # (REQUIRED) Password for that user + [Parameter(Mandatory=$true)] + [String] $SQLServiceUserPassword, + + [String] $PrimaryNode = ' ' + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($PrimaryNode.ToLower() -ne ($Env:ComputerName).ToLower()) { + Write-Log "THis function runs on AOAG primary node only." + Write-Log "Exiting." + return + } + + Write-Log "Installing 'RSAT-AD-PowerShell' ... " + Add-WindowsFeature RSAT-AD-PowerShell + + Import-Module ActiveDirectory + + $Creds = New-Credential -UserName "$DomainName\$UserName" -Password "$UserPassword" + + Write-Log "Adding new user ..." + $null = New-ADUser ` + -Name $SQLServiceUserName ` + -AccountPassword $(ConvertTo-SecureString -String $SQLServiceUserPassword -AsPlainText -Force) ` + -Credential $Creds ` + -ErrorAction 'Stop' + } +} diff --git a/muranorepository/Services/scripts/OptionParser.ps1 b/muranorepository/Services/scripts/OptionParser.ps1 index e69de29..664c4af 100644 --- a/muranorepository/Services/scripts/OptionParser.ps1 +++ b/muranorepository/Services/scripts/OptionParser.ps1 @@ -0,0 +1,280 @@ +function New-Option ([string]$Name, [switch]$Switch, [switch]$Boolean, [switch]$String, [switch]$List, $Constraints=$null) { + <# + .SYNOPSIS + Creates Option object + + .DESCRIPTION + Option object is a virtual object represtnting typed command line option. These objects encapsulate escaping and + validation matters. + + One and only one of the switches 'Switch', 'Boolean', 'String' or 'List' should be provided. + + .PARAMETER Name + Option name as it appears in the command line. + + .PARAMETER Switch + Use this switch to create valueless option (a switch). + + .PARAMETER Boolean + Use this switch to create boolean option. Its value is always converted to "1" or "0" + + .PARAMETER String + Use this switch to create string option. Its value will be properly quoted if necessary. + + .PARAMETER List + Use this switch to create option with list value. Values will be put into command line using valid value delemiter (a comma) + + .PARAMETER Constraints + When this parameter is specified, option values are limited to options from that list. + + #> + + $Option = New-Object -TypeName PSObject + + # Fields + $Option | Add-Member NoteProperty Type -value $null + $Option | Add-Member NoteProperty Name -value $null + $Option | Add-Member NoteProperty AllowedValues -value $null + + # Init + + $Option | Add-Member ScriptMethod __init__ { + param([string]$Name, $Switch, $Boolean, $String, $List) + + $this.Name = $Name + + # With respect for our developers we do not check for double type selected + if ($Switch) { + AugmentOptionSwitch($this) + } elseif ($Boolean) { + AugmentOptionBoolean($this) + } elseif ($String) { + AugmentOptionString($this) + } elseif ($List) { + AugmentOptionList($this) + } else { + throw "Switch, Boolean, String or List option type must be provided for option '$Name'" + } + } + + $Option | Add-Member ScriptMethod __post_init__ { + param($Constraints=$null) + if ($Constraints -ne $null) { + $this.AllowedValues = @() + $this.AllowedValues = $this.AllowedValues + $Constraints + } else { + $Constraints = $null + } + } + + # Methods + + $Option | Add-Member -Force ScriptMethod Validate { + if ($this.AllowedValues -ne $null) { + if (-not($this.AllowedValues -contains $this.Value)) { + $Cts = $this.AllowedValues -join ',' + throw "Option '$($this.Name)' may have values ($Cts) but not '$($this.Value)'" + } + } + } + + $Option | Add-Member -Force ScriptMethod ToString { + return "/$($this.Name)" + } + + # invoke constructor + + $Option.__init__($Name, $Switch, $Boolean, $String, $List) + $Option.__post_init__($Constraints) + + return $Option +} + +function AugmentOptionSwitch($Option) { +} + +function AugmentOptionBoolean($Option) { + # Fields + $Option | Add-Member NoteProperty Value -value $false + + # Methods + + $Option | Add-Member -Force ScriptMethod ToString { + if ($this.Value) { + return "/$($this.Name)=1" + } else { + return "/$($this.Name)=0" + } + } +} + +function AugmentOptionString($Option) { + # Fields + $Option | Add-Member NoteProperty Value -value "" + + # Methods + + $Option | Add-Member -Force ScriptMethod ToString { + $v = "$($this.Value)" + if ($v -match '.* .*') { + # TODO: Escape double quote characters if possible + return "/$($this.Name)=`"$v`"" + } else { + return "/$($this.Name)=$v" + } + } +} + +function AugmentOptionList($Option) { + # Fields + $Option | Add-Member NoteProperty Value -value @() + + # Methods + + $Option | Add-Member -Force ScriptMethod Validate { + if ($this.AllowedValues -ne $null) { + foreach ($V in $this.Value) { + if (-not($this.AllowedValues -contains $V)) { + $Cts = $this.AllowedValues -join ',' + throw "Option '$($this.Name)' may have values ($Cts) but not '$V'" + } + } + } + } + + $Option | Add-Member -Force ScriptMethod ToString { + return "/$($this.Name)=$($this.Value -join ',')" + } +} + +function New-OptionParser() { + <# + .SYNOPSIS + Creates OptionParser object. + + .DESCRIPTION + OptionParser object leverages Option objects capabilities and builds valid command line using specified options. + An application may also be invoked with OptionParser. + + #> + + $OptionParser = New-Object -TypeName PSObject + + # Fields + $OptionParser | Add-Member NoteProperty Options -value @{} + $OptionParser | Add-Member NoteProperty Defaults -value @{} + $OptionParser | Add-Member NoteProperty RequiredOptions -value @() + + # Methods + + $OptionParser | Add-Member ScriptMethod AddOption { + <# + .SYNOPSIS + Adds supported option into OptionParser. + + .DESCRIPTION + OptionParser does not allow using unrecognized options. Use this method to fill OptionParser with recognized options + + .PARAMETER Option + Option object + + .PARAMETER Required + Required option switch + + .PARAMETER Default + Option default value + #> + param($Option, [bool]$Required=$false, $Default=$null) + $this.Options.Add($Option.Name, $Option) + if ($Required) { + $this.RequiredOptions = $this.RequiredOptions + $Option.Name + if ($Option | Get-Member "Value") { + if ($Default) { + $this.Defaults.Add($Option.Name, $Default) + } + } else { + $this.Defaults.Add($Option.Name, $null) + } + } + } + + $OptionParser | Add-Member ScriptMethod Parse { + <# + .SYNOPSIS + Parses supplied options and returns command line parameters array. + + .DESCRIPTION + This method verifies that only supported options are provided, all mandatory options are in place, + all option meet constraints if any. Unspecified options with default values are added to command line. + So, mandatory option with default value never causes exception. + + .PARAMETER Options + A hash map of options to parse. Option names should be mapped to corresponding values. + #> + param([hashtable]$Options) + + $CommandLine = @() + foreach ($RequiredOptionName in $this.RequiredOptions) { + if (-not $Options.ContainsKey($RequiredOptionName)) { + $Default = $this.Defaults.Get_Item($RequiredOptionName) + if ($this.Defaults.ContainsKey($RequiredOptionName)) { + $Options.Add($RequiredOptionName, $this.Defaults.Get_Item($RequiredOptionName)) + } else { + throw "Required option '$RequiredOptionName' is missing" + } + } + } + + foreach ($OptionName in $($Options.keys)) { + $Option = $this.Options.Get_Item($OptionName) + if ($Option -eq $null) { + throw "Option '$OptionName' is not allowed" + } + if ($Option | Get-Member "Value") { + $Option.Value = $Options.Get_Item($OptionName) + } + $Option.Validate() + $CommandLine = $CommandLine + $Option.ToString() + } + return $CommandLine + } + + $OptionParser | Add-Member ScriptMethod ExecuteBinary { + param($Binary, [hashtable]$Options = @{}, $CommandLineSuffix = @()) + <# + .SYNOPSIS + Executes binary with a command line constructed from provided options. An arbitrary suffix may be + appended to the command line. + + .DESCRIPTION + This method uses OptionParser.Parse method to construct command line. If there a command line suffix + was supplied, it is appended to the end of command line. Normally command line suffix should contain + leading space character. + + Method waits for executable process to complete and returns its exit code. + + .PARAMETER Binary + Full or relative path to the executable to run. + + .PARAMETER Options + A hash map of options to pass to the executable. + + .PARAMETER CommandLineSuffix + Arbitrary command line suffix. Normally it shoud have leading space character. + #> + + $Binary = Get-Item $Binary + $CommandLine = $this.Parse($Options) + if ($CommandLineSuffix) { + $CommandLine = $CommandLine + $CommandLineSuffix + } + + Write-Log "Executing: $($Binary.FullName) $($CommandLine -join ' ')" + $process = [System.Diagnostics.Process]::Start($Binary, $CommandLine) + $process.WaitForExit() + $process.Refresh() + return $process.ExitCode + } + + return $OptionParser +} diff --git a/muranorepository/Services/scripts/SQLServerForAOAG.ps1 b/muranorepository/Services/scripts/SQLServerForAOAG.ps1 index e69de29..ae40b73 100644 --- a/muranorepository/Services/scripts/SQLServerForAOAG.ps1 +++ b/muranorepository/Services/scripts/SQLServerForAOAG.ps1 @@ -0,0 +1,538 @@ + +trap { + &$TrapHandler +} + +function Install-SqlServerPowerShellModule { + param ( + [String] $SetupRoot = '' + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ((Get-Module SQLPS -ListAvailable) -ne $null) { + Write-Log "Module SQLSP already installed." + return + } + + if ($MuranoFileShare -eq '') { + $MuranoFileShare = [String]([Environment]::GetEnvironmentVariable('MuranoFileShare')) + if ($MuranoFileShare -eq '') { + throw "Unable to find MuranoFileShare path." + } + } + Write-LogDebug "MuranoFileShare = '$MuranoFileShare'" + + if ($SetupRoot -eq '') { + $SetupRoot = [IO.Path]::Combine("$MuranoFileShare", 'Prerequisites\SQL Server\Tools') + } + Write-LogDebug "SetupRoot = '$SetupRoot'" + + $FileList = @( + 'SQLSysClrTypes.msi', + 'SharedManagementObjects.msi', + 'PowerShellTools.msi' + ) + + foreach ($MsiFile in $FileList) { + Write-Log "Trying to install '$MsiFile' ..." + $MsiPath = Join-Path $SetupRoot $MsiFile + if ([IO.File]::Exists($MsiPath)) { + Write-Log "Starting msiexe ..." + $Result = Exec -FilePath "msiexec.exe" -ArgumentList @('/i', "`"$MsiPath`"", '/quiet') -PassThru + if ($Result.ExitCode -ne 0) { + throw "Installation of MSI package '$MsiPath' failed with error code '$($Result.ExitCode)'" + } + } + else { + Write-Log "File '$MsiPath' not found." + } + } + } +} + + + +function Install-SqlServerForAOAG { + param ( + # Path to folder where msi files for additional SQL features are located + [String] $SetupRoot = '', + + # Path to folder where msi files for additional SQLPS module are located + [String] $SqlpsSetupRoot = '', + + [String] $MuranoFileShare = '', + + # (REQUIRED) Domain name + [String] $SQLServiceUserDomain = 'fc-acme.local', + + # (REQUIRED) User name for the account which will be used by SQL service + [String] $SQLServiceUserName = 'Administrator', + + # (REQUIRED) Password for that user + [String] $SQLServiceUserPassword = 'P@ssw0rd', + + [Switch] $UpdateEnabled + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($MuranoFileShare -eq '') { + $MuranoFileShare = [String]([Environment]::GetEnvironmentVariable('MuranoFileShare')) + if ($MuranoFileShare -eq '') { + throw "Unable to find MuranoFileShare path." + } + } + Write-LogDebug "MuranoFileShare = '$MuranoFileShare'" + + if ($SetupRoot -eq '') { + $SetupRoot = [IO.Path]::Combine("$MuranoFileShare", 'Prerequisites\SQL Server\2012') + } + Write-LogDebug "SetupRoot = '$SetupRoot'" + + $ExtraOptions = @{} + + if ($UpdateEnabled) { + $ExtraOptions += @{'UpdateEnabled' = $true} + } + else { + $ExtraOptions += @{'UpdateEnabled' = $false} + } + + $null = New-SQLServerForAOAG ` + -SetupRoot $SetupRoot ` + -SQLSvcUsrDomain $SQLServiceUserDomain ` + -SQLSvcUsrName $SQLServiceUserName ` + -SQLSvcUsrPassword $SQLServiceUserPassword ` + -ExtraOptions $ExtraOptions + } +} + + + +function Initialize-AlwaysOnAvailabilityGroup { + param ( + [String] $DomainName, + [String] $DomainAdminAccountName, + [String] $DomainAdminAccountPassword, + [String] $SqlServiceAccountName, + [String] $PrimaryNode, + [String] $ShareName = 'SharedWorkDir' + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + $ShareNetworkPath = '\\' + $PrimaryNode + '\' + $ShareName + + $DomainAdminAccountCreds = New-Credential ` + -UserName "$DomainName\$DomainAdminAccountName" ` + -Password "$DomainAdminAccountPassword" + + $FunctionsFile = Export-Function 'Get-NextFreePort', 'Initialize-AlwaysOn' + + $null = Start-PowerShellProcess @" +trap { + `$_ + exit 1 +} + +Import-Module CoreFunctions + +Write-Log "Importing functions file '$FunctionsFile' ..." +. "$FunctionsFile" + +Write-Log "Starting 'Initialize-AlwaysOn' ..." +`$XmlFile = [IO.Path]::Combine("$ShareNetworkPath", "`$(`$Env:ComputerName).xml") +Write-Log "Output XML file is '`$XmlFile'" +Initialize-AlwaysOn | Export-CliXml -Path `$XmlFile +"@ -Credential $DomainAdminAccountCreds -NoBase64 + } +} + + +function New-SharedFolderForAOAG { + param ( + # (OPTIONAL) + [String] $SharePath = [IO.Path]::Combine($Env:SystemDrive + '\', 'SharedWorkDir'), + + # (OPTIONAL) + [String] $ShareName = 'SharedWorkDir', + + [String] $PrimaryNode = ' ' + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($PrimaryNode.ToLower() -ne ($Env:ComputerName).ToLower()) { + Write-Log "This script runs on primary node only." + Write-Log "Exiting script." + return + } + + if ($ShareName -eq '') { + $ShareName = [IO.Path]::GetFileNameWithoutExtension($SharePath) + } + + Write-LogDebug "SharePath = '$SharePath'" + Write-LogDebug "ShareName = '$ShareName'" + + try { + Write-LogDebug "Trying to remove share '$ShareName'" + $null = Get-SmbShare -Name $ShareName -ErrorAction 'Stop' + $null = Remove-SmbShare -Name $ShareName -Force + write-Log "Share '$ShareName' removed." + } + catch { + Write-LogWarning "Share '$ShareName' not exists or cannot be deleted." + } + + try { + Write-LogDebug "Trying to remove folder '$SharePath" + $null = Get-Item -Path $SharePath -ErrorAction 'Stop' + $null = Remove-Item -Path $SharePath -Recurse -Force + Write-Log "Folder '$SharePath' removed." + } + catch { + Write-LogWarning "Folder '$SharePath' not exists or cannot be deleted." + } + + $null = New-Item -Path $SharePath -ItemType Container -Force + + $null = New-SmbShare -Path $SharePath ` + -Name $ShareName ` + -FullAccess "Everyone" ` + -Description "Shared folder for AlwaysOn Availability Group setup." + + return '\\' + $Env:ComputerName + '\' + $ShareName + } +} + + + +function New-DatabaseForAOAG { + param ( + [String] $DatabaseName, + [String] $DomainName, + [String] $UserName, + [String] $UserPassword + ) + + $Creds = New-Credential -UserName "$DomainName\$UserName" -Password "$UserPassword" + + $FunctionsFile = Export-Function 'Invoke-SQLText', 'ConvertTo-SQLName', 'ConvertTo-SQLString', 'New-SQLDatabase' + + Start-PowerShellProcess @" +trap { + `$_ + exit 1 +} + +Import-Module CoreFunctions + +Write-Log "Importing functions from file '$FunctionsFile' ..." +. "$FunctionsFile" + +Write-Log "Starting 'New-SQLDatabase' ..." +New-SQLDatabase $DatabaseName +"@ -Credential $Creds -NoBase64 +} + + + +function Initialize-AOAGPrimaryReplica { + param ( + # (OPTIONAL) Name of the new Availability Group. If not specified then default name will be used. + [String] $GroupName = 'MuranoAG', + + # (REQUIRED) Nodes that will be configured as replica partners. + #[Parameter(Mandatory=$true)] + [String[]] $NodeList, + + # (REQUIRED) Node name that will be primary for selected Availability Group + #[Parameter(Mandatory=$true)] + [String] $PrimaryNode, + + # (REQUIRED) Database list that will be added to the Availability Group + #[Parameter(Mandatory=$true)] + [String[]] $DatabaseList, + + # (REQUIRED) Listener name that will be used by clients to connect to databases in that AG + #[Parameter(Mandatory=$true)] + [String] $ListenerName = 'MuranoAG_Listener', + + # (REQUIRED) IP address of the listener + #[Parameter(Mandatory=$true)] + [String] $ListenerIP, + + [String] $ListenerIPMask = '255.255.255.0', + + [String] $ListenerPort = '5023', + + # Sync Mode Node List + [String[]] $SyncModeNodeList, + + [String] $SharedWorkDir = 'SharedWorkDir', + + [String] $CliXmlFile = '', + + [String] $DomainName, + [String] $UserName, + [String] $UserPassword + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Write-Log "Primary node: '$($PrimaryNode.ToLower())'" + Write-Log "Current node: '$(($Env:ComputerName).ToLower())'" + + if ($PrimaryNode.ToLower() -ne $($Env:ComputerName).ToLower()) { + Write-Log "This function works on PrimaryNode only." + Write-Log "Exiting." + return + } + + if ($CliXmlFile -eq '') { + $ReplicaDefinitionList = @() + foreach ($Node in $NodeList) { + try { + $NodeEndpointPort = Import-CliXml -Path "\\$PrimaryNode\SharedWorkDir\$Node.xml" + } + catch { + Write-Log "Using default endpoint port 5022" + $NodeEndpointPort = 5022 + } + + $ReplicaDefinition = @{ + "SERVER_INSTANCE" = "$Node"; + "ENDPOINT_URL" = "TCP://${Node}:${NodeEndpointPort}"; + "AVAILABILITY_MODE" = "ASYNCHRONOUS_COMMIT"; + "FAILOVER_MODE"="MANUAL"; + } + + if ($SyncModeNodeList -contains $Node) { + Write-Log "$Node is in SyncModeNodeList" + $ReplicaDefinition['AVAILABILITY_MODE'] = "SYNCHRONOUS_COMMIT" + $ReplicaDefinition['FAILOVER_MODE'] = "AUTOMATIC" + } + else { + Write-Log "$Node is NOT in SyncModeNodeList" + } + + $ReplicaDefinitionList += @($ReplicaDefinition) + } + + $Preferences = @{} + + $ListenerDefinition = @{ + "NAME"=$ListenerName; + "PORT" = "$ListenerPort"; + "STATIC" = "$ListenerIP/$ListenerIPMask" + } + + $Parameters = @{ + 'WorkDir' = "\\$PrimaryNode\$SharedWorkDir"; + 'Name' = $GroupName; + 'DatabaseNames' = $DatabaseList; + 'ReplicaDefs' = $ReplicaDefinitionList; + 'Preferences' = $Preferences; + 'ListenerDef' = $ListenerDefinition; + } + + $null = Remove-Item -Path "\\$PrimaryNode\SharedWorkDir\*" -Force + + $CliXmlFile = [IO.Path]::GetTempFileName() + + Write-LogDebug "CliXml file: '$CliXmlFile'" + + $null = Export-CliXml -Path $CliXmlFile -InputObject $Parameters -Depth 10 + + $null = Initialize-AOAGPrimaryReplica ` + -CliXmlFile $CliXmlFile ` + -DomainName $DomainName ` + -UserName $UserName ` + -UserPassword $UserPassword ` + -PrimaryNode $PrimaryNode + + Write-LogDebug "Inner 'Initialize-AOAGPrimaryReplica' call completed." + } + else { + $Creds = New-Credential -UserName "$DomainName\$UserName" -Password "$UserPassword" + + $FunctionsFile = Export-Function -All + + $null = Start-PowerShellProcess @" +trap { + `$_ + exit 1 +} + +Import-Module CoreFunctions + +Write-Log "Importing functions from '$FunctionsFile' ..." +. "$FunctionsFile" + +Write-Log "Importing CliXml parameters file ..." +`$Parameters = Import-CliXml -Path $CliXmlFile + +Write-Log "Starting 'New-AlwaysOnAvailabilityGroup' ..." +New-AlwaysOnAvailabilityGroup `` + -WorkDir `$Parameters['WorkDir'] `` + -Name `$Parameters['Name'] `` + -DatabaseNames `$Parameters['DatabaseNames'] `` + -ReplicaDefs `$Parameters['ReplicaDefs'] `` + -Preferences `$Parameters['Preferences'] `` + -ListenerDef `$Parameters['ListenerDef'] +"@ -Credential $Creds -NoBase64 + } + } +} + + + +function Initialize-AOAGSecondaryReplica { + param ( + # (REQUIRED) Nodes that will be configured as replica partners. + [Parameter(Mandatory=$true)] + [String[]] $NodeList, + + # (REQUIRED) Node name that will be primary for selected Availability Group + [Parameter(Mandatory=$true)] + [String] $PrimaryNode, + + [String] $SharedWorkDir = 'SharedWorkDir', + + [String] $DomainName, + [String] $UserName, + [String] $UserPassword + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($PrimaryNode.ToLower() -eq ($Env:ComputerName).ToLower()) { + Write-Log "This function works on any SecondaryNode only." + Write-Log "Exiting." + return + } + + $Creds = New-Credential -UserName "$DomainName\$UserName" -Password "$UserPassword" + + $FunctionsFile = Export-Function -All + + $null = Start-PowerShellProcess @" +trap { + $_ + exit 1 +} + +Import-Module CoreFunctions + +Write-Log "Importing functions from '$FunctionsFile' ..." +. "$FunctionsFile" + +Write-Log "Starting 'New-AlwaysOnAvailabilityGroupReplica' ..." +New-AlwaysOnAvailabilityGroupReplica -WorkDir "\\$PrimaryNode\$SharedWorkDir" +"@ -Credential $Creds -NoBase64 + } +} + + + +function Disable-Firewall { + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + netsh advfirewall set allprofiles state off + } +} + + + +function Enable-Firewall { + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + netsh advfirewall set allprofiles state on + } +} + + + +function Enable-TrustedHosts { + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force + } +} diff --git a/muranorepository/Services/scripts/SQLServerInstaller.ps1 b/muranorepository/Services/scripts/SQLServerInstaller.ps1 index e69de29..5a4001f 100644 --- a/muranorepository/Services/scripts/SQLServerInstaller.ps1 +++ b/muranorepository/Services/scripts/SQLServerInstaller.ps1 @@ -0,0 +1,1373 @@ +Import-Module NetSecurity + +function Test-Key([string]$path, [string]$key) { + if(!(Test-Path $path)) { return $false } + if ((Get-ItemProperty $path).$key -eq $null) { return $false } + return $true +} + +function Resolve-SQLServerPrerequisites { + <# + .SYNOPSIS + Installs MS SQL Server prerequisites (.Net Framework 3.5) + + .DESCRIPTION + Installs MS SQL Server prerequisites (.Net Framework 3.5) + + #> + if (-not (Test-Key "HKLM:\Software\Microsoft\NET Framework Setup\NDP\v3.5" "Install")) { + Import-Module ServerManager + Write-Host ".Net Framework 3.5 not found. Installing it using Server Manager..." + $Feature = Get-WindowsFeature NET-Framework + if ($Feature -eq $null) { + # We are probably on Windows Server 2012 + $Feature = Get-WindowsFeature NET-Framework-Core + } + if (-not $Feature) { + throw ".Net framework 3.5 feature was not found." + } + if (-not $Feature.DisplayName -match "3.5") { + Log-Warning ".Net framework 3.5 is required, but $($Feature.DisplayName) is available as Windows feature. Proceeding with installation" + } + [void](Add-WindowsFeature $Feature) + } +} + +function New-SQLServer { + <# + .SYNOPSIS + Installs new MS SQL Server instance. Returns $true if a reboot is required after the installation, + $false if a reboot is not required and throws an exception in case if installation fails. + + .DESCRIPTION + Installs new MS SQL Server instance in unattended mode. + + .PARAMETER SetupRoot + MS SQL Server installation files root directory. Normally it is just DVD drive name. + + .PARAMETER ExtraFeatures + List of features to be installed in addition to default "SQLEngine", "Conn", "SSMS", "ADV_SSMS". + #> + + param( + [parameter(Mandatory = $true)] + [string]$SetupRoot, + [array]$ExtraFeatures = @(), + [Hashtable]$ExtraOptions = @{} + ) + + $SetupDir = Get-Item $SetupRoot + $SetupExe = $SetupDir.GetFiles("setup.exe")[0] + + Resolve-SQLServerPrerequisites + + $parser = New-OptionParserInstall + $ExitCode = $parser.ExecuteBinary($SetupExe.FullName, @{"Q" = $null; "FEATURES" = @("SQLEngine", "Conn", "SSMS", "ADV_SSMS") + $ExtraFeatures} + $ExtraOptions) + + if ($ExitCode -eq 3010) { + return $true + } + + if ($ExitCode -ne 0) { + throw "Installation executable exited with code $("{0:X8}" -f $ExitCode) (Decimal: $ExitCode)" + } + + return $false +} + +function New-SQLServerForAOAG { + <# + .SYNOPSIS + Installs new MS SQL Server instance with all needed features to set up AlwaysOn Availability Group. + Returns $true if a reboot is required after the installation, $false if a reboot is not required + and throws an exception in case if installation fails. + + .DESCRIPTION + Installs new MS SQL Server instance in unattended mode. All features for AlwaysOn Availability Groups are + installed. + + All availability group members must be installed with the same SQLSvcUsrDoman, SQLSvcUsrName and SQLSvcUsrPassword parameters. + User must be a domain user since it will be used for nodes interconnection. + + .PARAMETER SetupRoot + MS SQL Server installation files root directory. Normally it is just DVD drive name. + + .PARAMETER SQLSvcUsrDomain + MS SQL Server user account domain name. + + .PARAMETER SQLSvcUsrName + MS SQL Server user account name. + + .PARAMETER SQLSvcUsrPassword + MS SQL Server user account password. + + .PARAMETER ExtraFeatures + List of features to be removed besides "SQLEngine", "Conn", "SSMS", "ADV_SSMS", "DREPLAY_CTLR", "DREPLAY_CLT". + #> + + param( + [parameter(Mandatory = $true)] + [string]$SetupRoot, + [parameter(Mandatory = $true)] + [string]$SQLSvcUsrDomain, + [parameter(Mandatory = $true)] + [string]$SQLSvcUsrName, + [parameter(Mandatory = $true)] + [string]$SQLSvcUsrPassword, + [array]$ExtraFeatures = @(), + [Hashtable]$ExtraOptions = @{} + ) + + $SetupDir = Get-Item $SetupRoot + $SetupExe = $SetupDir.GetFiles("setup.exe")[0] + + $SQLUser = "$SQLSvcUsrDomain\$SQLSvcUsrName" + $domain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$SQLSvcUsrDomain", $SQLSvcUsrName, $SQLSvcUsrPassword) + + if ($domain.name -eq $null) { + throw "Credentials validation failed for user $SQLUser. Check domain, login name and password." + } + + Resolve-SQLServerPrerequisites + + $parser = New-OptionParserInstall + $ExitCode = $parser.ExecuteBinary($SetupExe.FullName, @{"Q" = $null; "FEATURES" = @("SQLEngine", "Conn", "SSMS", "ADV_SSMS", "DREPLAY_CTLR", "DREPLAY_CLT") + $ExtraFeatures; + "AGTSVCACCOUNT" = $SQLUser; "AGTSVCPASSWORD" = $SQLSvcUsrPassword; "ASSVCACCOUNT" = $SQLUser; "ASSVCPASSWORD" = $SQLSvcUsrPassword; "ASSYSADMINACCOUNTS" = $SQLUSer; + "SQLSVCACCOUNT" = $SQLUser; "SQLSVCPASSWORD" = $SQLSvcUsrPassword; "SQLSYSADMINACCOUNTS" = $SQLUser; "ISSVCACCOUNT" = $SQLUser; "ISSVCPASSWORD" = $SQLSvcUsrPassword; + "RSSVCACCOUNT" = $SQLUser; "RSSVCPASSWORD" = $SQLSvcUsrPassword} + $ExtraOptions) + + if ($ExitCode -eq 3010) { + return $true + } + + if ($ExitCode -ne 0) { + throw "Installation executable exited with code $("{0:X8}" -f $ExitCode) (Decimal: $ExitCode)" + } + + return $false +} + +function Remove-SQLServer { + <# + .SYNOPSIS + Uninstalls MS SQL Server instance installed with New-SQLServer cmdlet + + .DESCRIPTION + Uninstalls MS SQL Server instance installed with New-SQLServer cmdlet in unattended mode + + .PARAMETER SetupRoot + MS SQL Server installation files root directory. Normally it is just DVD drive name. + + .PARAMETER ExtraFeatures + List of features to be removed besides "SQLEngine", "Conn", "SSMS", "ADV_SSMS". + #> + + param( + [parameter(Mandatory = $true)] + [string]$SetupRoot, + [array]$ExtraFeatures = @() + ) + + $SetupDir = Get-Item $SetupRoot + $SetupExe = $SetupDir.GetFiles("setup.exe")[0] + + $parser = New-OptionParserUninstall + $ExitCode = $parser.ExecuteBinary($SetupExe.FullName, @{"Q" = $null; "FEATURES" = @("SQLEngine", "Conn", "SSMS", "ADV_SSMS") + $ExtraFeatures}) + + if ($ExitCode -ne 0) { + throw "Installation executable exited with code $("{0:X8}" -f $ExitCode)" + } +} + +function Install-SQLServerForSysPrep { + <# + .SYNOPSIS + Installs new MS SQL Server in sysprep mode. + + .DESCRIPTION + Installs new MS SQL Server in sysprep mode. Returns $true if a reboot is required after the installation, + $false if a reboot is not required and throws an exception in case if installation fails. + + Setup must be completed after booting rearmed machine by using Complete-SQLServer cmdlet + + .PARAMETER SetupRoot + MS SQL Server installation files root directory. Normally it is just DVD drive name. + + .PARAMETER ExtraFeatures + List of features to be installed in addition to default "SQLEngine". Note that prior to + SQL Server version 2012 Service Pack 1 Cumulative Update 2 (January 2013) only "Replication", + "FullText" and "RS" may be installed in addition to "SQLEngine". See the following link for + detials: http://msdn.microsoft.com/en-us/library/ms144259.aspx + + #> +} + +function Install-SQLServerForSysPrep { + <# + .SYNOPSIS + Installs new MS SQL Server in sysprep mode. + + .DESCRIPTION + Installs new MS SQL Server in sysprep mode. Returns $true if a reboot is required after the installation, + $false if a reboot is not required and throws an exception in case if installation fails. + + Setup must be completed after booting rearmed machine by using Complete-SQLServer cmdlet + + .PARAMETER SetupRoot + MS SQL Server installation files root directory. Normally it is just DVD drive name. + + .PARAMETER ExtraFeatures + List of features to be installed in addition to default "SQLEngine". Note that prior to + SQL Server version 2012 Service Pack 1 Cumulative Update 2 (January 2013) only "Replication", + "FullText" and "RS" may be installed in addition to "SQLEngine". See the following link for + detials: http://msdn.microsoft.com/en-us/library/ms144259.aspx + + #> + + param( + [parameter(Mandatory = $true)] + [string]$SetupRoot, + [array]$ExtraFeatures = @() + ) + + $SetupDir = Get-Item $SetupRoot + $SetupExe = $SetupDir.GetFiles("setup.exe")[0] + + Resolve-SQLServerPrerequisites + + $parser = New-OptionParserPrepareImage + $ExitCode = $parser.ExecuteBinary($SetupExe.FullName, @{"QS" = $null; "FEATURES" = @("SQLEngine") + $ExtraFeatures }) + + if ($ExitCode -eq 3010) { + return $true + } + + if ($ExitCode -ne 0) { + throw "Installation executable exited with code $("{0:X8}" -f $ExitCode) (Decimal: $ExitCode)" + } + + return $false +} + +function Complete-SQLServerAfterSysPrep { + <# + .SYNOPSIS + Completes previously prepared with "Install-SQLServerForSysPrep" MS SQL Server after the system was rearmed. + + .DESCRIPTION + Completes previously prepared with "Install-SQLServerForSysPrep" MS SQL Server after the system was rearmed. + Returns $true if a reboot is required after the installation, $false if a reboot is not required and throws + an exception in case if installation fails. + + Setup must be completed after booting rearmed machine by using Complete-SQLServer cmdlet + + .PARAMETER SetupRoot + MS SQL Server installation files root directory. Normally it is just DVD drive name. + #> + + param( + [parameter(Mandatory = $true)] + [string]$SetupRoot + ) + + $SetupDir = Get-Item $SetupRoot + $SetupExe = $SetupDir.GetFiles("setup.exe")[0] + + $parser = New-OptionParserCompleteImage + $ExitCode = $parser.ExecuteBinary($SetupExe.FullName, @{"QS" = $null}) + + if ($ExitCode -eq 3010) { + return $true + } + + if ($ExitCode -ne 0) { + throw "Installation executable exited with code $("{0:X8}" -f $ExitCode) (Decimal: $ExitCode)" + } + + return $false +} + +function ConvertTo-SQLString { + <# + .SYNOPSIS + Converts argument to a valid SQL string in quotes + + .DESCRIPTION + Converts argument to a valid SQL string in quotes. The string may contain any characters. + See http://msdn.microsoft.com/en-us/library/ms179899.aspx + + .PARAMETER S + String to convert + #> + param( + [parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$S + ) + + return "'$($S -replace "'", "''")'" +} + +function ConvertTo-SQLName { + <# + .SYNOPSIS + Converts argument to a valid SQL name in brackets + + .DESCRIPTION + Converts argument to a valid SQL name in brackets. The string may contain any characters. + See http://msdn.microsoft.com/en-us/library/ms175874.aspx + + .PARAMETER S + String to convert + #> + param( + [parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$S + ) + return "[$($S -replace "]", "]]")]" +} + +function Invoke-SQLText { + <# + .SYNOPSIS + Invokes SQL text + + .DESCRIPTION + Invokes SQL text. Returns raw SQL server output. + + .PARAMETER SQL + SQL Text + + .PARAMETER User + SQL Server user name + + .PARAMETER Password + SQL Server user password + #> + param( + [parameter(Mandatory = $true)] + [string]$SQL, + [string]$User = $null, + [string]$Password = $null + ) + + #Write-Warning "$SQL`n" + #return + + $Binary = Get-Command "sqlcmd.exe" + + $tempFile = [IO.Path]::GetTempFileName() + $tempFile = Get-Item $tempFile + Set-Content -Path $tempFile -Value $SQL + + $CommandLine = @('-h', '-1', '-b', '-i', "`"$($tempFile.FullName)`"") + if (($User -ne $null) -and ($User -ne '')) { + $CommandLine = $CommandLine + '-U' + $CommandLine = $CommandLine + $User + $CommandLine = $CommandLine + '-P' + $CommandLine = $CommandLine + $Password + } + + Write-Debug "Executing: `n$SQL`n" + [string]$output = &$Binary $CommandLine + + $ExitCode = $LastExitCode + if ($ExitCode -ne 0) { + Write-Warning $output + throw "SQLCMD.EXE returned with exit code $ExitCode while running $Binary $CommandLine" + } + + Remove-Item $tempFile + + return $output +} + +function New-SQLUser { + <# + .SYNOPSIS + Invokes SQL text + + .DESCRIPTION + Invokes SQL text + + .PARAMETER SQL + SQL Text + + .PARAMETER User + SQL Server user name + + .PARAMETER Password + SQL Server user password + #> + param( + [parameter(Mandatory = $true)] + [string]$SQL, + [string]$User = $null, + [string]$Password = $null + ) +} + +function New-Password { + <# + .SYNOPSIS + Creates random password of the specified length + + .DESCRIPTION + Password contains random characters a-z, A-Z, numbers and special characters. + There is no guarantee that all the types of symbols will be present in the password. + + .PARAMETER Length + Desired length of the password. + + #> + param( + [parameter(Mandatory = $true)] + [int]$Length=6 + ) + + $Result = "" + $alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()'`"``_+[]\{}|;:,./<>?~" + while ($Length -gt 0) { + $x = Get-Random $alpha.Length + $c = $alpha[$x] + $Result = "$Result$c" + $Length = $Length - 1 + } + return $Result +} + +function Initialize-MirroringEndpoint { + <# + .SYNOPSIS + Creates mirroring endpoint. + + .DESCRIPTION + Master key is created if necessary. Host certificate is created when necessary either (normally on first endpoint creation). + + Endpoint and certificate are recreated in case if master key did not existed (should not normally happen). + + Endpoint is recreated in case if certificate did not existed (should not happen unless the endpoint was created manually). + + Mirroring endpoint is created unless one already exists. The endpoint is created with the specified name. When the endpoint + already exists is is unchanged. + + Endpoint port is selected automatically as 4022 or as first available port after 4022 in case if 4022 is already listening. + If there is no firewall rule with name 'DatabaseMirroring-TCP-{portnumber}', allowing rule is created. + + Certificate is stored in the specified file. + + Returns endpoint listening port. + + .PARAMETER EncryptionPassword + Encryption password used to create certificate. + + .PARAMETER CertificateFileName + Certificate target file name. File MUST NOT exist. + + #> + + param( + [parameter(Mandatory = $true)] + [String]$EncryptionPassword, + [parameter(Mandatory = $true)] + [String]$CertificateFileName + ) + + $EndpointName = 'MirroringEndpoint' + + $Folder = Get-Item $WorkDir + + $H = $Env:COMPUTERNAME -replace '[^A-Za-z0-9_]', '_' + + $Port = Get-NextFreePort 4022 + + $CreateMasterKey = "USE master; + + IF NOT EXISTS(select * from sys.symmetric_keys where name = '##MS_DatabaseMasterKey##') + BEGIN + CREATE MASTER KEY ENCRYPTION BY PASSWORD = $(ConvertTo-SQLString $EncryptionPassword); + IF EXISTS(select * from sys.certificates where name = '${H}_cert') + BEGIN + DROP CERTIFICATE ${H}_cert + END + IF EXISTS(SELECT * FROM sys.endpoints WHERE type_desc='DATABASE_MIRRORING') + BEGIN + DECLARE `@name VARCHAR(255) + SELECT TOP 1 `@name = name FROM sys.endpoints WHERE type_desc='DATABASE_MIRRORING' + EXEC ('DROP ENDPOINT [' + `@name + ']') + END + END + GO + + IF NOT EXISTS(select * from sys.certificates where name = '${H}_cert') + BEGIN + CREATE CERTIFICATE ${H}_cert WITH SUBJECT = '${H} endpoint certificate'; + IF EXISTS(SELECT * FROM sys.endpoints WHERE type_desc='DATABASE_MIRRORING') + BEGIN + DECLARE `@name VARCHAR(255) + SELECT TOP 1 `@name = name FROM sys.endpoints WHERE type_desc='DATABASE_MIRRORING' + EXEC ('DROP ENDPOINT [' + `@name + ']') + END + END + GO + + BACKUP CERTIFICATE ${H}_cert TO FILE = $(ConvertTo-SQLString "$CertificateFileName"); + GO + + DECLARE `@port int + IF EXISTS(SELECT * FROM sys.endpoints WHERE type_desc='DATABASE_MIRRORING') + BEGIN + SELECT `@port = port FROM sys.tcp_endpoints WHERE type_desc='DATABASE_MIRRORING' + END ELSE + BEGIN + CREATE ENDPOINT $(ConvertTo-SQLName $EndpointName) + STATE = STARTED + AS TCP ( + LISTENER_PORT = $Port + , LISTENER_IP = ALL + ) + FOR DATABASE_MIRRORING ( + AUTHENTICATION = CERTIFICATE ${H}_cert + , ENCRYPTION = REQUIRED ALGORITHM AES + , ROLE = ALL + ); + SELECT `@port = $Port + END + + SELECT 'port:(' + CONVERT(VARCHAR, `@port) + ')' as port + GO + + " + + $rawdata = Invoke-SQLText -SQL $CreateMasterKey + [int]$Port = $rawdata -replace '.*port:\(([^)]*)\).*', '$1' + + # Open port in Windows Firewall + + $PortOpen = $false + $RuleName = "DatabaseMirroring-TCP-$Port" + Get-NetFirewallRule | Foreach-Object { + if ($_.Name -eq $RuleName) { + $PortOpen = $true + } + } + if (-not $PortOpen) { + $DisplayName = "MS SQL Database Mirroring Endpoint at TCP port $Port" + New-NetFirewallRule -Name $RuleName -DisplayName $DisplayName -Description $DisplayName -Protocol TCP -LocalPort $Port -Enabled True -Profile Any -Action Allow + } + + return $Port +} + +function Complete-MirroringEndpoint { + <# + .SYNOPSIS + Completes mirroring endpoint + + .DESCRIPTION + Allows inbound connections from remote host + #> + + param( + [parameter(Mandatory = $true)] + [String]$RemoteHostName, + [parameter(Mandatory = $true)] + [String]$RemoteWorkDir, + [String]$RemoteHostLogin, + [String]$RemoteHostUser, + [String]$RemoteHostPassword + ) + + $Folder = Get-Item $RemoteWorkDir + $RemoteWorkDir = $Folder.FullName + + $H = $RemoteHostName -replace '[^A-Za-z0-9_]', '_' + + if (-not $RemoteHostLogin) { + $RemoteHostLogin = "${H}_login" + } + if (-not $RemoteHostUser) { + $RemoteHostUser = "${H}_user" + } + if (-not $RemoteHostPassword) { + $RemoteHostPassword = "$(New-Password 10)aA#3" + } + + $SQL = "USE master; + + IF NOT EXISTS(select * from sys.sql_logins where name=$(ConvertTo-SQLString $RemoteHostLogin)) + BEGIN + CREATE LOGIN $(ConvertTo-SQLName $RemoteHostLogin) WITH PASSWORD = $(ConvertTo-SQLString $RemoteHostPassword); + END + GO + + IF NOT EXISTS(select * from sys.sysusers where name=$(ConvertTo-SQLString $RemoteHostUser)) + BEGIN + CREATE USER $(ConvertTo-SQLName $RemoteHostUser) FOR LOGIN $(ConvertTo-SQLName $RemoteHostLogin); + END + GO + + IF EXISTS(select * from sys.certificates where name='${H}_remote_cert') + BEGIN + DROP CERTIFICATE ${H}_remote_cert + END + GO + + CREATE CERTIFICATE ${H}_remote_cert AUTHORIZATION $(ConvertTo-SQLName $RemoteHostUser) FROM FILE = $(ConvertTo-SQLString "$RemoteWorkDir\certificate.cer"); + GO + + DECLARE `@name VARCHAR(255) + SELECT TOP 1 `@name = name FROM sys.endpoints WHERE type_desc='DATABASE_MIRRORING' + SELECT 'name:(' + `@name + ')' as name + " + + $rawdata = Invoke-SQLText -SQL $SQL + $EndpointName = $rawdata -replace '.*name:\(([^)]*)\).*', '$1' + $SQL = "GRANT CONNECT ON ENDPOINT::$(ConvertTo-SQLName $EndpointName) TO $(ConvertTo-SQLName $RemoteHostLogin)" + [void](Invoke-SQLText -SQL $SQL) +} + +function Complete-SQLMirror { + <# + .SYNOPSIS + Completes creation of mirrored SQL database + + .DESCRIPTION + This cmdlet should be first executed on mirror server and then on principal server. + Otherwise it will fail (however it may be executed again with no harm). + #> + + param( + [parameter(Mandatory = $true)] + [String]$RemoteHostName, + [parameter(Mandatory = $true)] + [Int]$RemotePort, + [parameter(Mandatory = $true)] + [String]$DatabaseName + ) + + $Url = "TCP://${RemoteHostName}:${RemotePort}" + $AlterDb = "ALTER DATABASE $(ConvertTo-SQLName $DataBaseName) SET PARTNER = $(ConvertTo-SQLString $Url); + GO" + [void](Invoke-SQLText -SQL $AlterDb) +} + +function New-SQLDatabase { + <# + .SYNOPSIS + Creates empty SQL database + + .DESCRIPTION + Creates empty SQL database with default settings. Fails in case is the database already exists. + + .PARAMETER DataBaseName + Database name. + + .PARAMETER mdfFile + Name of the MDF (data) file. If not specified, the following value is used: + "C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\{DataBasePathName}.mdf" + Where {DataBasePathName} is database name with all but A-Z, a-z, 0-9 characters + replaced by underscore. + + .PARAMETER DataBaseName + Name of the LDF (transaction log) file. If not specified, the following value is used: + "C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\{DataBasePathName}_log.mdf" + Where {DataBasePathName} is database name with all but A-Z, a-z, 0-9 characters + replaced by underscore. + #> + + param( + [parameter(Mandatory = $true)] + [String]$DataBaseName, + [String]$mdfFile=$null, + [String]$ldfFile=$null + ) + + $DataBasePathName = $DataBaseName -replace '[^0-9a-zA-Z]', '_' + if (-not $mdfFile) { + $mdfFile = "C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\${DataBasePathName}.mdf" + } + if (-not $ldfFile) { + $ldfFile = "C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\${DataBasePathName}_log.ldf" + } + + $NewDatabase = " + CREATE DATABASE $(ConvertTo-SQLName $DataBaseName) + CONTAINMENT = NONE + ON PRIMARY + ( NAME = N$(ConvertTo-SQLString $DataBaseName), FILENAME = N$(ConvertTo-SQLString $mdfFile) , SIZE = 4096KB , FILEGROWTH = 1024KB ) + LOG ON + ( NAME = N$(ConvertTo-SQLString "${DataBaseName}_log"), FILENAME = N$(ConvertTo-SQLString $ldfFile) , SIZE = 1024KB , FILEGROWTH = 10%) + GO + USE $(ConvertTo-SQLName $DataBaseName) + GO + IF NOT EXISTS (SELECT name FROM sys.filegroups WHERE is_default=1 AND name = N'PRIMARY') ALTER DATABASE $(ConvertTo-SQLName $DataBaseName) MODIFY FILEGROUP [PRIMARY] DEFAULT + GO" + + [void](Invoke-SQLText -SQL $NewDatabase) +} + +function Initialize-SQLMirroringPrincipalStep1 { + <# + .SYNOPSIS + Prepares principal SQL Server for database mirroring (Stage 1) + + .DESCRIPTION + Initializes mirroring endpoint (this is absolutely symmetric step to the mirror init). In addition to that it creates + a database and stores backups of it and its transaction log in the same directory as the endpoint certificate. + + A firewall rule is created for endpoint if necessary. + + .PARAMETER WorkDir + Workind directory. This directory should be tranferred to the mirror server after this + step is executed. + + .PARAMETER DatabaseName + Mirrored database name. This name MUST be use at mirror server either. + + #> + + param( + [parameter(Mandatory = $true)] + [String]$WorkDir, + [parameter(Mandatory = $true)] + [String]$DataBaseName + ) + + [String]$EncryptionPassword = "$(New-Password 10)aA#3" + + if (-not (Test-Path $WorkDir)) { + [void](New-Item -Type Directory $WorkDir) + } + $WorkDir = (Get-Item $WorkDir).FullName + if ((Get-ChildItem -Path $WorkDir).Length -gt 0) { + throw "Working directory $WorkDir is not empty" + } + + $EndpointPort = Initialize-MirroringEndpoint $EncryptionPassword "$WorkDir\certificate.cer" + $EndpointPort | Set-Content "$WorkDir\endpoint-port.txt" + New-SQLDatabase $DataBaseName + + $BackupDb = "BACKUP DATABASE $(ConvertTo-SQLName $DataBaseName) TO DISK = N$(ConvertTo-SQLString "$WorkDir\Source.bak") WITH NOFORMAT, INIT, NAME = N'Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10 + GO" + [void](Invoke-SQLText -SQL $BackupDb) + $BackupLog = "BACKUP LOG $(ConvertTo-SQLName $DataBaseName) TO DISK = N$(ConvertTo-SQLString "$WorkDir\Source_log.bak") WITH NOFORMAT, INIT, NAME = N'Transaction Log Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10 + GO" + [void](Invoke-SQLText -SQL $BackupLog) +} + +function Initialize-SQLMirroringPrincipalStep2 { + <# + .SYNOPSIS + Prepares principal SQL Server for database mirroring (Stage 2) + + .DESCRIPTION + Imports remote server certificate and grants it with access to the mirroring endpoint. + + .PARAMETER RemoteHostName + Remote (mirror) host name. FQDN is preferred, but NetBIOS names and IP addresses are also accepted. + + .PARAMETER RemoteWorkDir + Path to a copy of workdir obtained from mirror machine created on Stage 1. + #> + + param( + [parameter(Mandatory = $true)] + [String]$RemoteHostName, + [parameter(Mandatory = $true)] + [String]$RemoteWorkDir + ) + + if (-not (Test-Path $RemoteWorkDir)) { + throw "Remote work dir '$RemoteWorkDir' was not found" + } + $RemoteWorkDir = (Get-Item $RemoteWorkDir).FullName + + Complete-MirroringEndpoint $RemoteHostName $RemoteWorkDir +} + +function Initialize-SQLMirroringPrincipalStep3 { + <# + .SYNOPSIS + Prepares principal SQL Server for database mirroring (Stage 3) + + .DESCRIPTION + Completes mirror creation. This step must be globally the last one in mirror creation sequence. + + Note that the remote host certificate is valid from the time it is created there. So + this step will fail if there is noticable different in time local and remote machines. + + .PARAMETER RemoteHostName + Remote (principal) host name. FQDN is preferred, but NetBIOS names and IP addresses are also accepted. + + .PARAMETER RemoteWorkDir + Path to a copy of workdir obtained from principal machine created on Stage 1. + + .PARAMETER DatabaseName + Mirrored database name. This name MUST match principal database name and name provided on step 1. + #> + + param( + [parameter(Mandatory = $true)] + [String]$RemoteHostName, + [parameter(Mandatory = $true)] + [String]$RemoteWorkDir, + [parameter(Mandatory = $true)] + [String]$DatabaseName + ) + + [int]$port = Get-Content "${RemoteWorkDir}\endpoint-port.txt" + Complete-SQLMirror $RemoteHostName $port $DatabaseName +} + +function Initialize-SQLMirroringMirrorStep1 { + <# + .SYNOPSIS + Prepares mirror SQL Server for database mirroring (Stage1) + + .DESCRIPTION + Initializes mirroring endpoint for mirror server. Stores mirroring endpoint certificate in Workdir. + + .PARAMETER WorkDir + Workind directory. This directory should be tranferred to the principal server after this + step is executed. + + .PARAMETER DatabaseName + Mirrored database name. This name MUST match principal database name. + + #> + + param( + [parameter(Mandatory = $true)] + [String]$WorkDir, + [parameter(Mandatory = $true)] + [String]$DatabaseName + ) + + [String]$EncryptionPassword = "$(New-Password 10)aA#3" + + if (-not (Test-Path $WorkDir)) { + [void](New-Item -Type Directory $WorkDir) + } + $WorkDir = (Get-Item $WorkDir).FullName + + $EndpointPort = Initialize-MirroringEndpoint $EncryptionPassword "$WorkDir\certificate.cer" + $EndpointPort | Set-Content "$WorkDir\endpoint-port.txt" +} + +function Initialize-SQLMirroringMirrorStep2 { + <# + .SYNOPSIS + Prepares mirror SQL Server for database mirroring (Stage 2) + + .DESCRIPTION + Imports remote server certificate and grants it with access to the mirroring endpoint. + Restores database obtained from principal and leaves it in 'Restoring' state. + + .PARAMETER RemoteHostName + Remote (principal) host name. FQDN is preferred, but NetBIOS names and IP addresses are also accepted. + + .PARAMETER RemoteWorkDir + Path to a copy of workdir obtained from principal machine created on Stage 1. + + .PARAMETER DatabaseName + Mirrored database name. This name MUST match principal database name. + + #> + + param( + [parameter(Mandatory = $true)] + [String]$RemoteHostName, + [parameter(Mandatory = $true)] + [String]$RemoteWorkDir, + [parameter(Mandatory = $true)] + [String]$DataBaseName + ) + + if (-not (Test-Path $RemoteWorkDir)) { + throw "Remote work dir '$RemoteWorkDir' was not found" + } + $RemoteWorkDir = (Get-Item $RemoteWorkDir).FullName + + Complete-MirroringEndpoint $RemoteHostName $RemoteWorkDir + + $RestoreDb = "RESTORE DATABASE $(ConvertTo-SQLName $DataBaseName) FROM DISK = N$(ConvertTo-SQLString "$RemoteWorkDir\Source.bak") WITH FILE = 1, NORECOVERY, NOUNLOAD, REPLACE, STATS = 5 + GO" + [void](Invoke-SQLText -SQL $RestoreDb) + $RestoreLog = "RESTORE LOG $(ConvertTo-SQLName $DataBaseName) FROM DISK = N$(ConvertTo-SQLString "$RemoteWorkDir\Source_log.bak") WITH FILE = 1, NORECOVERY, NOUNLOAD, STATS = 10 + GO" + [void](Invoke-SQLText -SQL $RestoreLog) +} + +function Initialize-SQLMirroringMirrorStep3 { + <# + .SYNOPSIS + Prepares mirror SQL Server for database mirroring (Stage 3) + + .DESCRIPTION + Completes mirror creation. This step must be executed strictly before symmetric step on the principal. + + Note that the remote host certificate is valid from the time it is created there. So + this step will fail if there is noticable different in time local and remote machines. + + .PARAMETER RemoteHostName + Remote (principal) host name. FQDN is preferred, but NetBIOS names and IP addresses are also accepted. + + .PARAMETER RemoteWorkDir + Path to a copy of workdir obtained from principal machine created on Stage 1. + + .PARAMETER DatabaseName + Mirrored database name. This name MUST match principal database name. + + #> + + param( + [parameter(Mandatory = $true)] + [String]$RemoteHostName, + [parameter(Mandatory = $true)] + [String]$RemoteWorkDir, + [parameter(Mandatory = $true)] + [String]$DatabaseName + ) + + [int]$port = Get-Content "${RemoteWorkDir}\endpoint-port.txt" + Complete-SQLMirror $RemoteHostName $port $DatabaseName +} + +function Get-NextFreePort { + <# + .SYNOPSIS + Returns specified desired port or closest next one unoccupied. + + .PARAMETER Port + Desired port number. + + #> + + param( + [parameter(Mandatory = $true)] + [int]$Port + ) + $OpenPorts = netstat -aon | select-string 'LISTENING' | Foreach-Object { (($_ -replace '^\s*', '' -split '\s+')[1] -split '.*:')[1] } | Sort-Object | Get-Unique + while ($OpenPorts.Contains(${Port})) { + $Port = $Port + 1 + } + return $Port +} + +function Initialize-AlwaysOn { + <# + .SYNOPSIS + Initializes AlwaysOn clustering on local SQL server and creates AlwaysOn endpoint listener. Returns AlwaysOn endpoint port number. + + .DESCRIPTION + Enables AlwaysOn clustering on local SQL server. Creates AlwaysOn TCP endpoint on port 5022 or greater if the one is occupied. + #> + + if (!(Test-Path SQLSERVER:\)) { + Import-Module sqlps + } + $MachineName = (Get-ChildItem SQLSERVER:\SQL)[0].PSChildName + $InstanceName = (Get-ChildItem SQLSERVER:\SQL\$MachineName).PSChildName + $AlwaysOnEnabled = ((Get-Item SQLSERVER:\SQL\$MachineName\$InstanceName) | select IsHadrEnabled).IsHadrEnabled + if (-not $AlwaysOnEnabled) { + Enable-SqlAlwaysOn -Path "SQLSERVER:\SQL\$MachineName\$InstanceName" -Force + } + $Instance = Get-Item SQLSERVER:\SQL\$MachineName\$InstanceName + $endpoint = $Instance.Endpoints["AlwaysOnEndpoint"] + if (-not $endpoint) { + $Port = Get-NextFreePort 5022 + $endpoint = New-SqlHadrEndpoint AlwaysOnEndpoint -Port $Port -Path SQLSERVER:\SQL\$MachineName\$InstanceName + } else { + $Port = $endpoint.Protocol.Tcp.ListenerPort + } + if ($endpoint.EndpointState -ne "Started") { + $endpoint.Start() + } + return $Port +} + +function New-AlwaysOnAvailabilityGroup { + <# + .SYNOPSIS + Creates new AlwaysOn availability group on primary replica. + + .DESCRIPTION + Creates new AlwaysOn availability group on primary replica. + + .PARAMETER WorkDir + Workind directory. This directory should be tranferred to the replica server(s) after this + step is executed. + + .PARAMETER Name + Availability group name. + + .PARAMETER DatabaseNames + Replica database(s) names. + + .PARAMETER ReplicaDefs + Array of replica definition. Each definition is a hash table with replica-specific values. + + Mandatory replica definition values are: + + * [String] SERVER_INSTANCE - Replica server instance name + * [String] ENDPOINT_URL - Replica server endpoint URL. Normally it is TCP://fully.qualified.domain.name:5022 + Port number should be obtained with Initialize-AlwaysOn at the replica server + * [String] AVAILABILITY_MODE - Replica availability mode. Can be "SYNCHRONOUS_COMMIT" or "ASYNCHRONOUS_COMMIT" only. + * [String] FAILOVER_MODE - Replica availability mode. Can be "MANUAL" or "AUTOMATIC" only. + + Optional replica definition values are: + + * [Integer] BACKUP_PRIORITY - Backup priority + * [Integer] SESSION_TIMEOUT - Session timeout + * [String] P_ALLOW_CONNECTIONS - Allowed connection types for "Primary" replica mode. Can be "READ_WRITE" or "ALL" only. + * [Array] P_READ_ONLY_ROUTING_LIST - List of replicas proviring readonly access when this one is primary. + * [String] S_ALLOW_CONNECTIONS - Allowed connection types for "Secondary" replica mode. Can be one of "NO", "READ_ONLY", "ALL". + * [String] S_READ_ONLY_ROUTING_URL - Replica read-only requests listener URL. Normally default server listener at port 1433 is used. + + .PARAMETER Preferences + Hash table of general availability group preferences. All the keys are optional. Supported entry keys are: + + * [String] AUTOMATED_BACKUP_PREFERENCE - Automated backup preference. Can be "PRIMARY", "SECONDARY_ONLY", "SECONDARY" or "NONE". + * [String] FAILURE_CONDITION_LEVEL - Failure condition level. Can be "1", "2", "3", "4" or "5". + * [Integer] HEALTH_CHECK_TIMEOUT - Replica health check timeout. + + .PARAMETER ListenerDef + Hash table containing availability group listener configuration. + + Mandatory listener configuration values are: + + [String] NAME - Listener name. + + Optional listener configuration values are: + + [String] PORT - Listener port number. Integer value may be suffixed by a "+" symol (such as "5022+") which allows the routine to + select next free port with number greater or equal to the specified value. + [String] DHCP - DHCP listener address configuration flag. When any value specified, DHCP is used to configure listener + (this is also the default behavior). Also, a specific interface for DHCP may be specified as IP_ADDRESS/MASK + (like "192.168.1.0/255.255.255.0") as a value of the parameter. + [Array] STATIC - Static IP addresses to listen. IP addresses may be IPv4 addresses in the "IP_ADDRESS/MASK" form or IPv6 + addresses in standard IPv6 notation. + + See http://msdn.microsoft.com/en-us/library/ff878399.aspx page for more details regarding all the supported options. + #> + + param( + [parameter(Mandatory = $true)] + [String]$WorkDir, + [parameter(Mandatory = $true)] + [String]$Name, + [parameter(Mandatory = $true)] + [Array]$DatabaseNames, + [parameter(Mandatory = $true)] + [Array]$ReplicaDefs, + [parameter] + [Hashtable]$Preferences, + [parameter(Mandatory = $true)] + [Hashtable]$ListenerDef + ) + + if (-not (Test-Path $WorkDir)) { + [void](New-Item -Type Directory $WorkDir) + } + $WorkDir = (Get-Item $WorkDir).FullName + if ((Get-ChildItem -Path $WorkDir).Length -gt 0) { + throw "Working directory $WorkDir is not empty" + } + + $QuotedDBNames = ($DatabaseNames | ForEach-Object { ConvertTo-SQLName $_ }) -join ", " + + if ($Preferences -eq $null) { + $Preferences = @() + } + $Prefs = @() + foreach($Pref in $Preferences) { + if ($Pref.Key -eq $null) { + Continue + } + if ($Pref.Key -eq "AUTOMATED_BACKUP_PREFERENCE") { + $Prefs = $Prefs + (Validate-Option $Pref.Key, $Pref.Value, @("PRIMARY", "SECONDARY_ONLY", "SECONDARY", "NONE") | New-ReplicaOption -Name $Pref.Key) + } elseif ($Pref.Key -eq "FAILURE_CONDITION_LEVEL") { + $Prefs = $Prefs + (Validate-Option $Pref.Key, $Pref.Value, @("1", "2", "3", "4", "5") | New-ReplicaOption -Name $Pref.Key) + } elseif ($Pref.Key -eq "HEALTH_CHECK_TIMEOUT") { + $Prefs = $Prefs + (Validate-IntOption $Pref.Key, $Pref.Value | New-ReplicaOption -Name $Pref.Key) + } else { + throw "Unexpected peferences option: '$($Pref.Key)'" + } + } + + $ReplicaDefinitionsArray = @() + for ($i = 0; $i -lt $ReplicaDefs.Length; $i++) { + $RDef = $ReplicaDefs[$i] + if ($RDef.GetType().Name -ne "Hashtable") { + throw "All elements of ReplicaDefs array should be Hashtables" + } + + $ReplicaOpts = @() + + # Mandatory options + $ReplicaName = Validate-DefinedOption "SERVER_INSTANCE" $RDef["SERVER_INSTANCE"] + $ReplicaOpts = $ReplicaOpts + (Validate-DefinedOption "ENDPOINT_URL" $RDef["ENDPOINT_URL"] | ConvertTo-SQLString | New-ReplicaOption -Name "ENDPOINT_URL") + $ReplicaOpts = $ReplicaOpts + (Validate-Option "AVAILABILITY_MODE" $RDef["AVAILABILITY_MODE"] @("SYNCHRONOUS_COMMIT", "ASYNCHRONOUS_COMMIT") | New-ReplicaOption -Name "AVAILABILITY_MODE") + $ReplicaOpts = $ReplicaOpts + (Validate-Option "FAILOVER_MODE" $RDef["FAILOVER_MODE"] @("AUTOMATIC", "MANUAL") | New-ReplicaOption -Name "FAILOVER_MODE") + + # Optional options + if ($RDef["BACKUP_PRIORITY"] -ne $null) { + $ReplicaOpts = $ReplicaOpts + (Validate-IntOption "BACKUP_PRIORITY" $RDef["BACKUP_PRIORITY"] | New-ReplicaOption -Name "BACKUP_PRIORITY") + } + if ($RDef["SESSION_TIMEOUT"] -ne $null) { + $ReplicaOpts = $ReplicaOpts + (Validate-IntOption "SESSION_TIMEOUT" $RDef["SESSION_TIMEOUT"] | New-ReplicaOption -Name "SESSION_TIMEOUT") + } + + $SecondaryRole = @() + if ($RDef["S_ALLOW_CONNECTIONS"] -ne $null) { + $SecondaryRole = $SecondaryRole + (Validate-Option "S_ALLOW_CONNECTIONS" $RDef["S_ALLOW_CONNECTIONS"] @("NO", "READ_ONLY", "ALL") | New-ReplicaOption -Name "ALLOW_CONNECTIONS") + } + if ($RDef["S_READ_ONLY_ROUTING_URL"] -ne $null) { + $SecondaryRole = $SecondaryRole + ($RDef["S_READ_ONLY_ROUTING_URL"] | ConvertTo-SQLString | New-ReplicaOption -Name "ALLOW_CONNECTIONS") + } + if ($SecondaryRole.Length -gt 0) { + $ReplicaOpts = $ReplicaOpts + ("( $($SecondaryRole -join ', ') )" | New-ReplicaOption -Name "SECONDARY_ROLE") + } + + $PrimaryRole = @() + if ($RDef["P_ALLOW_CONNECTIONS"] -ne $null) { + $PrimaryRole = $PrimaryRole + (Validate-Option "P_ALLOW_CONNECTIONS" $RDef["P_ALLOW_CONNECTIONS"] @("READ_WRITE", "ALL") | New-ReplicaOption -Name "ALLOW_CONNECTIONS") + } + if ($RDef["P_READ_ONLY_ROUTING_LIST"] -ne $null) { + $PrimaryRole = $PrimaryRole + ((($RDef["P_READ_ONLY_ROUTING_LIST"] | ForEach-Object { ConvertTo-SQLString $_ }) -join ', ') | New-ReplicaOption -Name "ALLOW_CONNECTIONS") + } + if ($PrimaryRole.Length -gt 0) { + $ReplicaOpts = $ReplicaOpts + ("( $($PrimaryRole -join ', ') )" | New-ReplicaOption -Name "PRIMARY_ROLE") + } + + $ReplicaDefinitionsArray = $ReplicaDefinitionsArray + + # TCP://bravo.murano.local:5022 + "N$(ConvertTo-SQLString $ReplicaName) WITH ($($ReplicaOpts -join ', '))" + } + $ReplicaDefinitions = $ReplicaDefinitionsArray -join ",`r`n "; + + if ($ListenerDef["DHCP"] -ne $null) { + if ($ListenerDef["DHCP"].matches("\d+\.\d+\.\d+\.\d+/\d+\.\d+\.\d+\.\d+")) { + ($IpAddr, $Mask) = $ListenerDef["DHCP"] -split "/" + $ListenerAddr = "DHCP ON ( $IpAddr, $Mask )" + } else { + $ListenerAddr = "DHCP" + } + } else { + [array]$IPAddresses = $ListenerDef["STATIC"] + if (($IPAddresses -eq $null) -or ($IPAddresses.Count -eq 0)) { + $ListenerAddr = "DHCP" + } else { + $ConvertedOpts = @() + foreach ($IpOption in $IPAddresses) { + # IPv4 + if ($IpOption -match "\d+\.\d+\.\d+\.\d+/\d+\.\d+\.\d+\.\d+") { + ($IpAddr, $Mask) = $IpOption -split "/" + $ConvertedOpts = $ConvertedOpts + "( $(ConvertTo-SQLString $IpAddr), $(ConvertTo-SQLString $Mask) )" + continue + } + # IPv6 + if ($IpOption -match "^(((?=(?>.*?::)(?!.*::)))(::)?([0-9A-F]{1,4}::?){0,5}|([0-9A-F]{1,4}:){6})(\2([0-9A-F]{1,4}(::?|$)){0,2}|((25[0-5]|(2[0-4]|1\d|[1-9])?\d)(\.|$)){4}|[0-9A-F]{1,4}:[0-9A-F]{1,4})(? + param( + [parameter(Mandatory = $true)] + [String]$WorkDir + ) + if (-not (Test-Path $WorkDir)) { + throw "Work dir '$WorkDir' not found" + } + $WorkDirObj = Get-Item -Path $WorkDir + $WorkDir = $WorkDirObj.FullName + $GroupName = Get-Content $WorkDirObj.GetFiles("avgroup.name").FullName + + $JoinGroup = "ALTER AVAILABILITY GROUP $(ConvertTo-SQLName $GroupName) JOIN + GO" + [void](Invoke-SQLText -SQL $JoinGroup) + + for ($i = 0; ; $i++) { + $File = $WorkDirObj.GetFiles("db$i.name") + if (-not $File) { + break; + } + $DataBaseName = Get-Content $WorkDirObj.GetFiles("db$i.name").FullName + $RestoreDb = "RESTORE DATABASE $(ConvertTo-SQLName $DataBaseName) FROM DISK = N$(ConvertTo-SQLString "$WorkDir\db$i.bak") WITH FILE = 1, NORECOVERY, NOUNLOAD, REPLACE, STATS = 5 + GO" + [void](Invoke-SQLText -SQL $RestoreDb) + $RestoreLog = "RESTORE LOG $(ConvertTo-SQLName $DataBaseName) FROM DISK = N$(ConvertTo-SQLString "$WorkDir\db$i.log.bak") WITH FILE = 1, NORECOVERY, NOUNLOAD, STATS = 10 + GO" + [void](Invoke-SQLText -SQL $RestoreLog) + $AlterDB = "ALTER DATABASE $(ConvertTo-SQLName $DataBaseName) SET HADR AVAILABILITY GROUP = $(ConvertTo-SQLName $GroupName) + GO" + [void](Invoke-SQLText -SQL $AlterDB) + } +} + +function New-ReplicaOption { + param( + [parameter(Mandatory = $true)] + [String]$Name, + [parameter(Mandatory = $true, ValueFromPipeline = $true)] + [String]$Value + ) + return "$Name = $Value" +} + +function Validate-Option { + <# + .SYNOPSIS + Checks that the value is one of allowed values + + .DESCRIPTION + Checks that the value is one of allowed values or throws exception otherwise. Returns provided value. + + .PARAMETER Name + Option name. Used only for error message. + + .PARAMETER Value + Option value. + + .PARAMETER Allowed + List of allowed option valus. + #> + param( + [parameter(Mandatory = $true)] + [String]$Name, + [String]$Value, + [Array]$Allowed + ) + if (($Value -eq $null) -or ($Value -eq "")) { + throw "No value was provided for $Name" + } + foreach ($V in $Allowed) { + if ($V -eq $Value) { + return $Value + } + } + throw "Provided value '$Value' for $Name is not one of $($Allowed -join ', ')" +} + +function Validate-IntOption { + <# + .SYNOPSIS + Checks that the value is integer + + .DESCRIPTION + Checks that the value is integer. Returns provided value. + + .PARAMETER Name + Option name. Used only for error message. + + .PARAMETER Value + Option value. + #> + param( + [parameter(Mandatory = $true)] + [String]$Name, + [parameter] + [String]$Value + ) + if (($Value -eq $null) -or ($Value -eq "")) { + throw "No value was provided for $Name" + } + if (-not ("$Value" -match "^[+-]?\d+$")) { + throw "Provided value '$Value' for $Name is not a number" + } + return $Value +} + +function Validate-DefinedOption { + <# + .SYNOPSIS + Checks that the value is not null + + .DESCRIPTION + Checks that the value is not null. Returns provided value. + + .PARAMETER Name + Option name. Used only for error message. + + .PARAMETER Value + Option value. + #> + param( + [parameter(Mandatory = $true)] + [String]$Name, + [parameter(Mandatory = $false)] + [String]$Value + ) + if (($Value -eq $null) -or ($Value -eq "")) { + throw "No value was provided for $Name" + } + return $Value +} + + + diff --git a/muranorepository/Services/scripts/SQLServerOptionParsers.ps1 b/muranorepository/Services/scripts/SQLServerOptionParsers.ps1 index e69de29..1624853 100644 --- a/muranorepository/Services/scripts/SQLServerOptionParsers.ps1 +++ b/muranorepository/Services/scripts/SQLServerOptionParsers.ps1 @@ -0,0 +1,367 @@ +function New-OptionParserInstall { + <# + .SYNOPSIS + Creates an option parser for MS SQL Server 2012 setup "INSTALL" action. + + .DESCRIPTION + Use this cmdlet to create an option parser for MS SQL Server 2012 setup "INSTALL" action. + All documented option are supported. See the following link for details: + http://msdn.microsoft.com/en-us/library/ms144259.aspx + #> + $OptionParser = New-OptionParser + + $IsPartOfDomain = (Get-WmiObject Win32_ComputerSystem).PartOfDomain + + $OptionParser.AddOption((New-Option "ACTION" -String -Constraints "INSTALL"), $true, "INSTALL") + $OptionParser.AddOption((New-Option "IACCEPTSQLSERVERLICENSETERMS" -Switch), $true) + $OptionParser.AddOption((New-Option "ENU" -Switch)) + #$OptionParser.AddOption((New-Option "UpdateEnabled" -Switch)) + $OptionParser.AddOption((New-Option "UpdateEnabled" -Boolean)) + $OptionParser.AddOption((New-Option "UpdateSource" -String)) + $OptionParser.AddOption((New-Option "CONFIGURATIONFILE" -String)) + $OptionParser.AddOption((New-Option "ERRORREPORTING" -Boolean)) + $OptionParser.AddOption((New-Option "FEATURES" -List -Constraints ("SQL","SQLEngine","Replication","FullText","DQ","AS","RS","DQC","IS","MDS","Tools","BC","BOL","BIDS","Conn","SSMS","ADV_SSMS","DREPLAY_CTLR","DREPLAY_CLT","SNAC_SDK","SDK","LocalDB"))) + $OptionParser.AddOption((New-Option "ROLE" -String -Constraints ("SPI_AS_ExistingFarm", "SPI_AS_NewFarm", "AllFeatures_WithDefaults"))) + $OptionParser.AddOption((New-Option "INDICATEPROGRESS" -Switch)) + $OptionParser.AddOption((New-Option "INSTALLSHAREDDIR" -String)) + $OptionParser.AddOption((New-Option "INSTALLSHAREDWOWDIR" -String)) + $OptionParser.AddOption((New-Option "INSTANCEDIR" -String)) + $OptionParser.AddOption((New-Option "INSTANCEID" -String)) + $OptionParser.AddOption((New-Option "INSTANCENAME" -String), $true, "MSSQLSERVER") + $OptionParser.AddOption((New-Option "PID" -String)) + $OptionParser.AddOption((New-Option "Q" -Switch)) + $OptionParser.AddOption((New-Option "QS" -Switch)) + $OptionParser.AddOption((New-Option "UIMODE" -String -Constraints ("Normal", "AutoAdvance"))) + $OptionParser.AddOption((New-Option "SQMREPORTING" -Boolean)) + $OptionParser.AddOption((New-Option "HIDECONSOLE" -Switch)) + $OptionParser.AddOption((New-Option "AGTSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "AGTSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "AGTSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "ASBACKUPDIR" -String)) + $OptionParser.AddOption((New-Option "ASCOLLATION" -String)) + $OptionParser.AddOption((New-Option "ASCONFIGDIR" -String)) + $OptionParser.AddOption((New-Option "ASDATADIR" -String)) + $OptionParser.AddOption((New-Option "ASLOGDIR" -String)) + $OptionParser.AddOption((New-Option "ASSERVERMODE" -String -Constraints ("MULTIDIMENSIONAL", "POWERPIVOT", "TABULAR"))) + $OptionParser.AddOption((New-Option "ASSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "ASSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "ASSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + + #$OptionParser.AddOption((New-Option "ASSYSADMINACCOUNTS" -String), $true, "$ENV:USERDOMAIN\$ENV:USERNAME") + if ($IsPartOfDomain) { + $OptionParser.AddOption((New-Option "ASSYSADMINACCOUNTS" -String), $true, "$Env:USERDOMAIN\Administrator") + } + else { + $OptionParser.AddOption((New-Option "ASSYSADMINACCOUNTS" -String), $true, "$Env:COMPUTERNAME\Administrator") + } + + $OptionParser.AddOption((New-Option "ASTEMPDIR" -String)) + $OptionParser.AddOption((New-Option "ASPROVIDERMSOLAP" -Boolean)) + $OptionParser.AddOption((New-Option "FARMACCOUNT" -String)) + $OptionParser.AddOption((New-Option "FARMPASSWORD" -String)) + $OptionParser.AddOption((New-Option "PASSPHRASE" -String)) + $OptionParser.AddOption((New-Option "FARMADMINIPORT" -String)) + $OptionParser.AddOption((New-Option "BROWSERSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "ENABLERANU" -Switch)) + $OptionParser.AddOption((New-Option "INSTALLSQLDATADIR" -String)) + $OptionParser.AddOption((New-Option "SAPWD" -String)) + $OptionParser.AddOption((New-Option "SECURITYMODE" -String -Constrainrs ("SQL"))) + $OptionParser.AddOption((New-Option "SQLBACKUPDIR" -String)) + $OptionParser.AddOption((New-Option "SQLCOLLATION" -String)) + $OptionParser.AddOption((New-Option "ADDCURRENTUSERASSQLADMIN" -Switch)) + $OptionParser.AddOption((New-Option "SQLSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "SQLSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "SQLSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + + #$OptionParser.AddOption((New-Option "SQLSYSADMINACCOUNTS" -String), $true, "$ENV:USERDOMAIN\$ENV:USERNAME") + if ($IsPartOfDomain) { + $OptionParser.AddOption((New-Option "SQLSYSADMINACCOUNTS" -String), $true, "$ENV:USERDOMAIN\Administrator") + } + else { + $OptionParser.AddOption((New-Option "SQLSYSADMINACCOUNTS" -String), $true, "$ENV:COMPUTERNAME\Administrator") + } + + $OptionParser.AddOption((New-Option "SQLTEMPDBDIR" -String)) + $OptionParser.AddOption((New-Option "SQLTEMPDBLOGDIR" -String)) + $OptionParser.AddOption((New-Option "SQLUSERDBDIR" -String)) + $OptionParser.AddOption((New-Option "SQLUSERDBLOGDIR" -String)) + $OptionParser.AddOption((New-Option "FILESTREAMLEVEL" -String -Constraints ("0", "1", "2", "3"))) + $OptionParser.AddOption((New-Option "FILESTREAMSHARENAME" -String)) + $OptionParser.AddOption((New-Option "FTSVCACCOUNT" -String)) + $OptionParser.AddOption((New-Option "FTSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "ISSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "ISSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "ISSVCStartupType" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "NPENABLED" -Boolean)) + $OptionParser.AddOption((New-Option "TCPENABLED" -Boolean)) + $OptionParser.AddOption((New-Option "RSINSTALLMODE" -String -Constraints ("SharePointFilesOnlyMode", "DefaultNativeMode", "FilesOnlyMode"))) + $OptionParser.AddOption((New-Option "RSSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "RSSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "RSSVCStartupType" -String -Constraints ("Manual", "Automatic", "Disabled"))) + + return $OptionParser +} + +function New-OptionParserPrepareImage { + <# + .SYNOPSIS + Creates an option parser for MS SQL Server 2012 setup "PrepareImage" action. + + .DESCRIPTION + Use this cmdlet to create an option parser for MS SQL Server 2012 setup "PrepareImage" action. + + Note that for installer version of MS SQL Server prior to 2012 SP1 Cumulative Update 2 only the + following features are supported: SQLEngine, Replication, FullText, RS + + All documented option are supported. See the following link for details: + http://msdn.microsoft.com/en-us/library/ms144259.aspx + #> + $OptionParser = New-OptionParser + + $OptionParser.AddOption((New-Option "ACTION" -String -Constraints "PrepareImage"), $true, "PrepareImage") + $OptionParser.AddOption((New-Option "IACCEPTSQLSERVERLICENSETERMS" -Switch), $true) + $OptionParser.AddOption((New-Option "ENU" -Switch)) + $OptionParser.AddOption((New-Option "UpdateEnabled" -Switch)) + $OptionParser.AddOption((New-Option "UpdateSource" -String)) + $OptionParser.AddOption((New-Option "CONFIGURATIONFILE" -String)) +# $OptionParser.AddOption((New-Option "FEATURES" -List -Constraints ("SQLEngine","Replication","FullText","RS"))) + $OptionParser.AddOption((New-Option "FEATURES" -List -Constraints ("SQL","SQLEngine","Replication","FullText","DQ","AS","RS","DQC","IS","MDS","Tools","BC","BOL","BIDS","Conn","SSMS","ADV_SSMS","DREPLAY_CTLR","DREPLAY_CLT","SNAC_SDK","SDK","LocalDB"))) + $OptionParser.AddOption((New-Option "HIDECONSOLE" -Switch)) + $OptionParser.AddOption((New-Option "INDICATEPROGRESS" -Switch)) + $OptionParser.AddOption((New-Option "INSTALLSHAREDDIR" -String)) + $OptionParser.AddOption((New-Option "INSTANCEDIR" -String)) + $OptionParser.AddOption((New-Option "INSTANCEID" -String), $true, "MSSQLSERVER") + $OptionParser.AddOption((New-Option "Q" -Switch)) + $OptionParser.AddOption((New-Option "QS" -Switch)) + + return $OptionParser +} + +function New-OptionParserPrepareImageSP1U2 { + <# + .SYNOPSIS + Creates an option parser for MS SQL Server 2012 setup "PrepareImage" action. + + .DESCRIPTION + Use this cmdlet to create an option parser for MS SQL Server 2012 setup "PrepareImage" action. + + This cmdlet should be used only for MS SQL Server 2012 SP1 Cimilative Update 2 or later. + + Note that for installer version of MS SQL Server prior to 2012 SP1 Cimilative Update 2 only the + following features are supported: SQLEngine, Replication, FullText, RS + + All documented option are supported. See the following link for details: + http://msdn.microsoft.com/en-us/library/ms144259.aspx + #> + $OptionParser = New-OptionParser + + $OptionParser.AddOption((New-Option "ACTION" -String -Constraints "PrepareImage"), $true, "PrepareImage") + $OptionParser.AddOption((New-Option "IACCEPTSQLSERVERLICENSETERMS" -Switch), $true) + $OptionParser.AddOption((New-Option "ENU" -Switch)) + $OptionParser.AddOption((New-Option "UpdateEnabled" -Switch)) + $OptionParser.AddOption((New-Option "UpdateSource" -String)) + $OptionParser.AddOption((New-Option "CONFIGURATIONFILE" -String)) + $OptionParser.AddOption((New-Option "FEATURES" -List -Constraints ("SQL","SQLEngine","Replication","FullText","DQ","AS","RS","DQC","IS","MDS","Tools","BC","BOL","BIDS","Conn","SSMS","ADV_SSMS","SNAC_SDK","SDK","LocalDB"))) + $OptionParser.AddOption((New-Option "HIDECONSOLE" -Switch)) + $OptionParser.AddOption((New-Option "INDICATEPROGRESS" -Switch)) + $OptionParser.AddOption((New-Option "INSTALLSHAREDDIR" -String)) + $OptionParser.AddOption((New-Option "INSTANCEDIR" -String)) + $OptionParser.AddOption((New-Option "INSTANCEID" -String), $true, "MSSQLSERVER") + $OptionParser.AddOption((New-Option "Q" -Switch)) + $OptionParser.AddOption((New-Option "QS" -Switch)) + + return $OptionParser +} + +function New-OptionParserCompleteImage { + <# + .SYNOPSIS + Creates an option parser for MS SQL Server 2012 setup "CompleteImage" action. + + .DESCRIPTION + Use this cmdlet to create an option parser for MS SQL Server 2012 setup "CompleteImage" action. + + Note that INSTANCEID parameter value MUST be the same as specified on "PrepareImage" phase. + + All documented option are supported. See the following link for details: + http://msdn.microsoft.com/en-us/library/ms144259.aspx + #> + $OptionParser = New-OptionParser + + $OptionParser.AddOption((New-Option "ACTION" -String -Constraints "CompleteImage"), $true, "CompleteImage") + $OptionParser.AddOption((New-Option "IACCEPTSQLSERVERLICENSETERMS" -Switch), $true) + $OptionParser.AddOption((New-Option "ENU" -Switch)) + $OptionParser.AddOption((New-Option "CONFIGURATIONFILE" -String)) + $OptionParser.AddOption((New-Option "ERRORREPORTING" -Boolean)) + $OptionParser.AddOption((New-Option "INDICATEPROGRESS" -Switch)) + $OptionParser.AddOption((New-Option "INSTANCEID" -String), $true, "MSSQLSERVER") + $OptionParser.AddOption((New-Option "INSTANCENAME" -String), $true, "MSSQLSERVER") + $OptionParser.AddOption((New-Option "PID" -String)) + $OptionParser.AddOption((New-Option "Q" -Switch)) + $OptionParser.AddOption((New-Option "QS" -Switch)) + $OptionParser.AddOption((New-Option "SQMREPORTING" -Boolean)) + $OptionParser.AddOption((New-Option "HIDECONSOLE" -Switch)) + $OptionParser.AddOption((New-Option "AGTSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "AGTSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "AGTSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "BROWSERSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "ENABLERANU" -Switch)) + $OptionParser.AddOption((New-Option "INSTALLSQLDATADIR" -String)) + $OptionParser.AddOption((New-Option "SAPWD" -String)) + $OptionParser.AddOption((New-Option "SECURITYMODE" -String -Constrainrs ("SQL"))) + $OptionParser.AddOption((New-Option "SQLBACKUPDIR" -String)) + $OptionParser.AddOption((New-Option "SQLCOLLATION" -String)) + $OptionParser.AddOption((New-Option "SQLSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "SQLSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "SQLSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "SQLSYSADMINACCOUNTS" -String), $true, "$ENV:USERDOMAIN\$ENV:USERNAME") + $OptionParser.AddOption((New-Option "SQLTEMPDBDIR" -String)) + $OptionParser.AddOption((New-Option "SQLTEMPDBLOGDIR" -String)) + $OptionParser.AddOption((New-Option "SQLUSERDBDIR" -String)) + $OptionParser.AddOption((New-Option "SQLUSERDBLOGDIR" -String)) + $OptionParser.AddOption((New-Option "FILESTREAMLEVEL" -String -Constraints ("0", "1", "2", "3"))) + $OptionParser.AddOption((New-Option "FILESTREAMSHARENAME" -String)) + $OptionParser.AddOption((New-Option "FTSVCACCOUNT" -String)) + $OptionParser.AddOption((New-Option "FTSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "NPENABLED" -Boolean)) + $OptionParser.AddOption((New-Option "TCPENABLED" -Boolean)) + $OptionParser.AddOption((New-Option "RSINSTALLMODE" -String -Constraints ("SharePointFilesOnlyMode", "DefaultNativeMode", "FilesOnlyMode"))) + $OptionParser.AddOption((New-Option "RSSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "RSSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "RSSVCStartupType" -String -Constraints ("Manual", "Automatic", "Disabled"))) + + return $OptionParser +} + +function New-OptionParserCompleteImageSP1U2 { + <# + .SYNOPSIS + Creates an option parser for MS SQL Server 2012 setup "CompleteImage" action. + + .DESCRIPTION + Use this cmdlet to create an option parser for MS SQL Server 2012 setup "CompleteImage" action. + + This cmdlet should be used only for MS SQL Server 2012 SP1 Cimilative Update 2 or later. + + All documented option are supported. See the following link for details: + http://msdn.microsoft.com/en-us/library/ms144259.aspx + #> + $OptionParser = New-OptionParser + + $OptionParser.AddOption((New-Option "ACTION" -String -Constraints "CompleteImage"), $true, "CompleteImage") + $OptionParser.AddOption((New-Option "IACCEPTSQLSERVERLICENSETERMS" -Switch), $true) + $OptionParser.AddOption((New-Option "ENU" -Switch)) + $OptionParser.AddOption((New-Option "CONFIGURATIONFILE" -String)) + $OptionParser.AddOption((New-Option "ERRORREPORTING" -Boolean)) + $OptionParser.AddOption((New-Option "INDICATEPROGRESS" -Switch)) + $OptionParser.AddOption((New-Option "INSTANCEID" -String)) + $OptionParser.AddOption((New-Option "INSTANCENAME" -String)) + $OptionParser.AddOption((New-Option "PID" -String)) + $OptionParser.AddOption((New-Option "Q" -Switch)) + $OptionParser.AddOption((New-Option "QS" -Switch)) + $OptionParser.AddOption((New-Option "SQMREPORTING" -Boolean)) + $OptionParser.AddOption((New-Option "HIDECONSOLE" -Switch)) + $OptionParser.AddOption((New-Option "AGTSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "AGTSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "AGTSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "BROWSERSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "ENABLERANU" -Switch)) + $OptionParser.AddOption((New-Option "INSTALLSQLDATADIR" -String)) + $OptionParser.AddOption((New-Option "SAPWD" -String)) + $OptionParser.AddOption((New-Option "SECURITYMODE" -String -Constrainrs ("SQL"))) + $OptionParser.AddOption((New-Option "SQLBACKUPDIR" -String)) + $OptionParser.AddOption((New-Option "SQLCOLLATION" -String)) + $OptionParser.AddOption((New-Option "SQLSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "SQLSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "SQLSVCSTARTUPTYPE" -String -Constraints ("Manual", "Automatic", "Disabled"))) + $OptionParser.AddOption((New-Option "SQLSYSADMINACCOUNTS" -String), $true, "$ENV:USERDOMAIN\$ENV:USERNAME") + $OptionParser.AddOption((New-Option "SQLTEMPDBDIR" -String)) + $OptionParser.AddOption((New-Option "SQLTEMPDBLOGDIR" -String)) + $OptionParser.AddOption((New-Option "SQLUSERDBDIR" -String)) + $OptionParser.AddOption((New-Option "SQLUSERDBLOGDIR" -String)) + $OptionParser.AddOption((New-Option "FILESTREAMLEVEL" -String -Constraints ("0", "1", "2", "3"))) + $OptionParser.AddOption((New-Option "FILESTREAMSHARENAME" -String)) + $OptionParser.AddOption((New-Option "FTSVCACCOUNT" -String)) + $OptionParser.AddOption((New-Option "FTSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "NPENABLED" -Boolean)) + $OptionParser.AddOption((New-Option "TCPENABLED" -Boolean)) + $OptionParser.AddOption((New-Option "RSINSTALLMODE" -String -Constraints ("SharePointFilesOnlyMode", "DefaultNativeMode", "FilesOnlyMode"))) + $OptionParser.AddOption((New-Option "RSSVCACCOUNT" -String), $true, "NT AUTHORITY\Network Service") + $OptionParser.AddOption((New-Option "RSSVCPASSWORD" -String)) + $OptionParser.AddOption((New-Option "RSSVCStartupType" -String -Constraints ("Manual", "Automatic", "Disabled"))) + + return $OptionParser +} + +function New-OptionParserUpgrade { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserEditionUpgrade { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserRepair { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserRebuilddatabase { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserUninstall { + <# + .SYNOPSIS + Creates an option parser for MS SQL Server 2012 setup "INSTALL" action. + + .DESCRIPTION + Use this cmdlet to create an option parser for MS SQL Server 2012 setup "INSTALL" action. + All documented option are supported. See the following link for details: + http://msdn.microsoft.com/en-us/library/ms144259.aspx + #> + $OptionParser = New-OptionParser + + $OptionParser.AddOption((New-Option "ACTION" -String -Constraints "UNINSTALL"), $true, "UNINSTALL") + $OptionParser.AddOption((New-Option "CONFIGURATIONFILE" -String)) + $OptionParser.AddOption((New-Option "FEATURES" -List -Constraints ("SQL","SQLEngine","Replication","FullText","DQ","AS","RS","DQC","IS","MDS","Tools","BC","BOL","BIDS","Conn","SSMS","ADV_SSMS","DREPLAY_CTLR","DREPLAY_CLT","SNAC_SDK","SDK","LocalDB")), $true) + $OptionParser.AddOption((New-Option "INDICATEPROGRESS" -Switch)) + $OptionParser.AddOption((New-Option "INSTANCENAME" -String), $true, "MSSQLSERVER") + $OptionParser.AddOption((New-Option "Q" -Switch)) + $OptionParser.AddOption((New-Option "HIDECONSOLE" -Switch)) + + return $OptionParser +} + +function New-OptionParserInstallFailoverCluster { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserPrepareFailoverCluster { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserCompleteFailoverCluster { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserUpgrade { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserAddNode { + # ToDo: Implement + throw "Not yet implemented" +} + +function New-OptionParserRemoveNode { + # ToDo: Implement + throw "Not yet implemented" +} diff --git a/muranorepository/Services/scripts/Set-LocalUserPassword.ps1 b/muranorepository/Services/scripts/Set-LocalUserPassword.ps1 index e69de29..8708a0f 100644 --- a/muranorepository/Services/scripts/Set-LocalUserPassword.ps1 +++ b/muranorepository/Services/scripts/Set-LocalUserPassword.ps1 @@ -0,0 +1,37 @@ + +trap { + &$TrapHandler +} + + +Function Set-LocalUserPassword { + param ( + [String] $UserName, + [String] $Password, + [Switch] $Force + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ((Get-WmiObject Win32_UserAccount -Filter "LocalAccount = 'True' AND Name='$UserName'") -eq $null) { + throw "Unable to find local user account '$UserName'" + } + + if ($Force) { + Write-Log "Changing password for user '$UserName' to '*****'" # :) + $null = ([ADSI] "WinNT://./$UserName").SetPassword($Password) + } + else { + Write-LogWarning "You are trying to change password for user '$UserName'. To do this please run the command again with -Force parameter." + } + } +} + diff --git a/muranorepository/Services/scripts/Start-PowerShellProcess.ps1 b/muranorepository/Services/scripts/Start-PowerShellProcess.ps1 index e69de29..779908d 100644 --- a/muranorepository/Services/scripts/Start-PowerShellProcess.ps1 +++ b/muranorepository/Services/scripts/Start-PowerShellProcess.ps1 @@ -0,0 +1,151 @@ + +trap { + &$TrapHandler +} + + + +function Select-CliXmlBlock { + param ( + [String] $Path, + [String] $OutFile = [IO.Path]::GetTempFileName() + ) + + $TagFound = $false + Get-Content $Path | + ForEach-Object { + if ($_ -eq '#< CLIXML') { + $TagFound = $true + } + if ($TagFound) { + Add-Content -Path $OutFile -Value $_ + } + } + $OutFile +} + + + +function Start-PowerShellProcess { + param ( + [String] $Command, + $Credential = $null, + [Switch] $IgnoreStdErr, + [Switch] $NoBase64 + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + $StdOut = [IO.Path]::GetTempFileName() + $StdErr = [IO.Path]::GetTempFileName() + + $ArgumentList = @('-OutputFormat', 'XML') + + if ($NoBase64) { + $TmpScript = [IO.Path]::GetTempFileName() + Rename-Item -Path "$TmpScript" -NewName "$TmpScript.ps1" -Force + $TmpScript = "$TmpScript.ps1" + + Write-LogDebug $TmpScript + + $Command | Out-File $TmpScript + + $ArgumentList += @('-File', "$TmpScript") + } + else { + $Bytes = [Text.Encoding]::Unicode.GetBytes($Command) + $EncodedCommand = [Convert]::ToBase64String($Bytes) + + Write-LogDebug $EncodedCommand + + $ArgumentList += @('-EncodedCommand', $EncodedCommand) + } + + Write-LogDebug $ArgumentList + + Write-Log "Starting external PowerShell process ..." + + if ($Credential -eq $null) { + $Process = Start-Process -FilePath 'powershell.exe' ` + -ArgumentList @($ArgumentList) ` + -RedirectStandardOutput $StdOut ` + -RedirectStandardError $StdErr ` + -NoNewWindow ` + -Wait ` + -PassThru + } + else { + $Process = Start-Process -FilePath 'powershell.exe' ` + -ArgumentList @($ArgumentList) ` + -RedirectStandardOutput $StdOut ` + -RedirectStandardError $StdErr ` + -Credential $Credential ` + -NoNewWindow ` + -Wait ` + -PassThru + } + + Write-Log "External PowerShell process exited with exit code '$($Process.ExitCode)'." + + #if ($ArgumentList -contains '-File') { + # Remove-Item -Path $TmpScript -Force + #} + + $ErrorActionPreferenceSaved = $ErrorActionPreference + $ErrorActionPreference = 'SilentlyContinue' + + Write-LogDebug "StdOut file is '$StdOut'" + Write-LogDebug "StdErr file is '$StdErr'" + + if ((Get-Item $StdOut).Length -gt 0) { + try { + Write-LogDebug "Loading StdOut from '$StdOut'" + $TmpFile = Select-CliXmlBlock $StdOut + $StdOutObject = Import-Clixml $TmpFile + Write-LogDebug "" + Write-LogDebug ($StdOutObject) + Write-LogDebug "" + $StdOutObject + #Remove-Item -Path $TmpFile -Force + } + catch { + Write-LogDebug "An error occured while loading StdOut from '$TmpFile'" + } + } + + if ((Get-Item $StdErr).Length -gt 0) { + try { + Write-LogDebug "Loading StdErr ..." + $TmpFile = Select-CliXmlBlock $StdErr + $StdErrObject = Import-Clixml $TmpFile + Write-LogDebug "" + Write-LogDebug ($StdErrObject) + Write-LogDebug "" + if (-not $IgnoreStdErr) { + $StdErrObject + } + #Remove-Item -Path $TmpFile -Force + } + catch { + Write-LogDebug "An error occured while loading StdErr from '$TmpFile'" + } + } + + $ErrorActionPreference = $ErrorActionPreferenceSaved + + if ($Process.ExitCode -ne 0) { + throw("External PowerShell process exited with code '$($Process.ExitCode)'") + } + + #Remove-Item $StdOut -Force + #Remove-Item $StdErr -Force + } +} diff --git a/muranorepository/Services/scripts/Update-ServiceConfig.ps1 b/muranorepository/Services/scripts/Update-ServiceConfig.ps1 index e69de29..791713d 100644 --- a/muranorepository/Services/scripts/Update-ServiceConfig.ps1 +++ b/muranorepository/Services/scripts/Update-ServiceConfig.ps1 @@ -0,0 +1,60 @@ + +trap { + &$TrapHandler +} + + + +function Update-ServiceConfig { + param ( + [String] $Name, + [String] $RunAsUser = '', + [String] $DomainName = '.', + [String] $Password = '', + [Switch] $RunAsLocalService + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + $ArgumentList = @('config', "`"$Name`"") + + if ($RunAsLocalService) { + $ArgumentList += @("obj=", "`"NT AUTHORITY\LocalService`"") + } + elseif ($RunAsUser -ne '') { + $ArgumentList += @("obj=", "`"$DomainName\$RunAsUser`"", "password=", "`"$Password`"") + } + + $Process = Exec 'sc.exe' $ArgumentList -PassThru -RedirectStreams + + if ($Process.ExitCode -ne 0) { + throw "Command 'sc.exe' returned exit code '$($Process.ExitCode)'" + } + + $NtRights = "C:\Murano\Tools\ntrights.exe" + + if (-not ([IO.File]::Exists($NtRights))) { + throw "File '$NtRights' not found." + } + + $Process = Exec $NtRights @('-u', "$DomainName\$RunAsUser", '+r', 'SeServiceLogonRight') -RedirectStreams -PassThru + + if ($Process.ExitCode -ne 0) { + throw "Command '$NtRights' returned exit code '$($Process.ExitCode)'" + } + + $Process = Exec $NtRights @('-u', "$DomainName\$RunAsUser", '+r', 'SeBatchLogonRight') -RedirectStreams -PassThru + + if ($Process.ExitCode -ne 0) { + throw "Command '$NtRights' returned exit code '$($Process.ExitCode)'" + } + } +}