compute/sqlserver/powershell/create-sql-instance-availability-group-prerequisites.ps1 (331 lines of code) (raw):

#Requires -Version 5 # Copyright(c) 2020 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. # ############################################################################## #.SYNOPSIS # Run the prerequisites before running the script: # create-sql-instance-availability-group.ps1 # #.DESCRIPTION # The script create-sql-instance-availability-group.ps1 assumes you have a # custom network with 3 subnetworks. It also expects a Windows AD domain. # This script can be used to create all of those prerequisites. # # For more information see "Configuring SQL Server Availability Groups" at: # https://cloud.google.com/compute/docs/instances/sql-server/configure-availability # # You should run this script one section at a time since not all sections # may apply in your case # #.NOTES # AUTHOR: Anibal Santiago - @SQLThinker ############################################################################## Set-Location $PSScriptRoot ################################################################################ # Specify the parameters: # You can change these parameters to match your requirements. ################################################################################ # Network names $network = 'wsfcnet' # Name of the Custom network $subnet1_name = 'wsfcsubnet1' # Name of Subnet 1. Node 1 will reside here. $subnet2_name = 'wsfcsubnet2' # Name of Subnet 2. Node 2 will reside here. $subnet3_name = 'wsfcsubnet3' # Name of Subnet 3. The domain controller will reside here but it can also be one of the other 2 subnets. # Create 3 subnets. One for each node and the third one for the domain controller. Change as appropiate to match your network requirements. $subnet1 = "10.0.0.0/24" # Subnet1 netmask $subnet2 = "10.1.0.0/24" # Subnet2 netmask $subnet3 = "10.2.0.0/24" # Subnet3 netmask $region1 = "us-east1" # Subnet1 region $region2 = "us-east1" # Subnet2 region $region3 = "us-east4" # Subnet3 region # AD Domain configuration $DomainName = "dbeng.com"; $DomainMode = "Win2012R2"; $ForestMode = "Win2012R2"; $DatabasePath = "C:\Windows\NTDS"; $LogPath = "C:\Windows\NTDS"; $SysvolPath = "C:\Windows\SYSVOL"; # Domain controller configuration $domain_cntr = "dc-windows" # Name of the Windows Domain controller $ip_domain_cntr = '10.2.0.100' # IP of the Windows Domain controller $zone_domain_cntr = "us-east4-b" # Zone for domain controller (needs to be within $region3) $machine_type_domain_cntr = "n1-standard-1" # Machine Type $boot_disk_type_domain_cntr = "pd-standard" # Boot disk type (pd-ssd, pd-standard) $image_family_domain_cntr = "windows-2012-r2" # Windows Server image family (windows-2012-r2, windows-2016) $boot_disk_size_domain_cntr = "200GB" # Size of the boot disk # Get the password for Domain Admin if (!($domain_pwd)) { $admin_pwd = Read-Host -AsSecureString "Password for Domain Administrator" } else { $admin_pwd = ConvertTo-SecureString $domain_pwd -AsPlainText -Force } ################################################################################ # Create the custom network with 3 subnetworks ################################################################################ Write-Host "$(Get-Date) Create custom network with 3 subnetworks" $ErrorActionPreference = 'continue' #To skip non-error reported by PowerShell Invoke-Command -ScriptBlock { if ( !( Get-GceNetwork | Where Name -eq $network ) ) { gcloud compute networks create $network --subnet-mode custom # Remove-GceNetwork -Name $network # New-GceNetwork -Name $network -IPv4Range 10.0.0.0/22 } else { Write-Host " The network: $network already exist" } if ( !( Get-GceNetwork | Where {$_.Name -eq $network -and ` $_.Subnetworks -like "*$subnet1_name" } ) ) { gcloud compute networks subnets create $subnet1_name ` --network $network ` --region $region1 ` --range $subnet1 } else { Write-Host " The subnetwork: $subnet1_name already exist" } if ( !( Get-GceNetwork | Where {$_.Name -eq $network -and ` $_.Subnetworks -like "*$subnet2_name" } ) ) { gcloud compute networks subnets create $subnet2_name ` --network $network ` --region $region2 ` --range $subnet2 } else { Write-Host " The subnetwork: $subnet2_name already exist" } if ( !( Get-GceNetwork | Where {$_.Name -eq $network -and ` $_.Subnetworks -like "*$subnet3_name" } ) ) { gcloud compute networks subnets create $subnet3_name ` --network $network ` --region $region3 ` --range $subnet3 } else { Write-Host " The subnetwork: $subnet3_name already exist" } } 2> $null $ErrorActionPreference = 'Stop' ################################################################################ # Create the firewall rules ################################################################################ Write-Host "$(Get-Date) Create firewall: Allow RDP, WinRM and Internal Traffic" # Find the public IP and network URL $myip = Invoke-RestMethod http://ipinfo.io/json | Select -ExpandProperty ip $network_url = Get-GceNetwork -Name $network | Select -ExpandProperty SelfLink # Allow all connections between the instances $tcp = New-GceFirewallProtocol -IPProtocol tcp -Port 1-65535 $udp = New-GceFirewallProtocol -IPProtocol udp -Port 1-65535 $icmp = New-GceFirewallProtocol -IPProtocol icmp if ( !( Get-GceFirewall | Where {$_.Name -eq "allow-internal-ports"} ) ) { Add-GceFirewall ` -Name "allow-internal-ports" ` -Network $network_url ` -SourceRange $subnet1,$subnet2,$subnet3 ` -AllowedProtocol $tcp, $udp, $icmp } else { Write-Host " The firewall: allow-internal-ports already exist" } # Allow RDP from any IP address <# $tcp = New-GceFirewallProtocol -IPProtocol tcp -Port 3389 if ( !( Get-GceFirewall | Where {$_.Name -eq "allow-rdp"} ) ) { Add-GceFirewall ` -Name "allow-rdp" ` -Network $network_url ` -SourceRange "0.0.0.0/0" ` -AllowedProtocol $tcp } else { Write-Host " The firewall: allow-rdp already exist" } #> # Allow WinRM from any IP address <# $tcp = New-GceFirewallProtocol -IPProtocol tcp -Port 5986 if ( !( Get-GceFirewall | Where {$_.Name -eq "allow-winrm"} ) ) { Add-GceFirewall ` -Name "allow-winrm" ` -Network $network_url ` -SourceRange "0.0.0.0/0" ` -AllowedProtocol $tcp } else { Write-Host " The firewall: allow-winrm already exist" } #> # Allow WinRM from current IP $tcp = New-GceFirewallProtocol -IPProtocol tcp -Port 5986 if ( !( Get-GceFirewall | Where {$_.Name -eq "allow-winrm-current-ip"} ) ) { Add-GceFirewall ` -Name "allow-winrm-current-ip" ` -Network $network_url ` -SourceRange "$($myip)" ` -AllowedProtocol $tcp } else { Write-Host " The firewall: allow-winrm-current-ip already exist" } # Allow RDP from current IP $tcp = New-GceFirewallProtocol -IPProtocol tcp -Port 3389 if ( !( Get-GceFirewall | Where {$_.Name -eq "allow-rdp-current-ip"} ) ) { Add-GceFirewall ` -Name "allow-rdp-current-ip" ` -Network $network_url ` -SourceRange "$($myip)" ` -AllowedProtocol $tcp } else { Write-Host " The firewall: allow-rdp-current-ip already exist" } ################################################################################ # Create a windows instance to be the domain controller # Then create the domain and assign a password for the domain administrator ################################################################################ Write-Host "$(Get-Date) Create Instance: $domain_cntr" $ErrorActionPreference = 'continue' #To skip non-error reported by PowerShell if ( !( Get-GceInstance | Where {$_.Name -eq $domain_cntr} ) ) { Invoke-Command -ScriptBlock { gcloud compute instances create $domain_cntr ` --machine-type $machine_type_domain_cntr ` --boot-disk-type $boot_disk_type_domain_cntr ` --image-project windows-cloud ` --image-family $image_family_domain_cntr ` --boot-disk-size $boot_disk_size_domain_cntr ` --zone $zone_domain_cntr ` --subnet $subnet3_name ` --private-network-ip $ip_domain_cntr; } 2> $null # Wait until the instance is configured # We wait for the text 'Instance setup finished' to show in the Serial Port $creation_status = Get-GceInstance -zone $zone_domain_cntr -Name $domain_cntr -SerialPortOutput | Select-String -Pattern 'Instance setup finished' -Quiet while (!($creation_status)) { Write-Host "$(Get-Date) Waiting for instance $domain_cntr to be ready" Start-Sleep -s 30 $creation_status = Get-GceInstance -zone $zone_domain_cntr -Name $domain_cntr -SerialPortOutput | Select-String -Pattern 'Instance setup finished' -Quiet } Write-Host "$(Get-Date) Instance $domain_cntr is now ready" ################################################################################ # Generate a password in the domain controller for username config.user ################################################################################ Write-Host "$(Get-Date) Create/reset password for config.user in $domain_cntr" $ErrorActionPreference = 'continue' #To skip non-error reported by PowerShell $results = Invoke-Command -ScriptBlock { gcloud compute reset-windows-password $domain_cntr ` --zone $zone_domain_cntr ` --user "config.user" ` --quiet 2> $null } | ConvertFrom-String $ErrorActionPreference = 'Stop' # Grab the IP and Password from the results of the 'gcloud.exe' command $ip_address = $results.P2[0] $password = ConvertTo-SecureString -String $results.P2[1] -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential("config.user", $password) ################################################################################ # Create a remote session to the domain controller ################################################################################ $session_options = New-PSSessionOption -SkipCACheck -SkipCNCheck ` -SkipRevocationCheck $session = New-PSSession -ComputerName $ip_address -UseSSL ` -Credential $credential -SessionOption $session_options ################################################################################ # Enable the local Administrator account. It is needed to create a domain. ################################################################################ Invoke-Command -Session $session -ScriptBlock { Write-Host "$(Get-Date) Enabling the Administrator account" $admin_user = ([ADSI]'WinNT://localhost/Administrator,user') if ($admin_user.userflags.value -band "0x0002"){ $admin_user.userflags.value = $admin_user.userflags.value -bxor "0x0002" } # Generate a 36 characters random password $password = -join(33..122 | %{[char]$_} | Get-Random -C 36) $admin_user.SetPassword($password) $admin_user.SetInfo() } ################################################################################ # Create the Windows domain ################################################################################ Write-Host "$(Get-Date) Promote $domain_cntr to a Domain Controller" Invoke-Command -Session $session -ScriptBlock { param($admin_pwd, $DomainName, $DomainMode, $DatabasePath, $LogPath, ` $SysvolPath, $ForestMode) # Find information about the instance $computer_info = Get-WmiObject -Class Win32_ComputerSystem # Setup the computer as domain controller, only if not setup yet if ( $computer_info.Domain -eq "WORKGROUP" ) { Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools Install-ADDSForest -CreateDnsDelegation:$false ` -DatabasePath $DatabasePath ` -LogPath $LogPath ` -SysvolPath $SysvolPath ` -DomainName $DomainName ` -DomainMode $DomainMode ` -ForestMode $ForestMode ` -InstallDNS:$true ` -NoRebootOnCompletion:$false ` -SafeModeAdministratorPassword $admin_pwd ` -Force:$true Write-Host "$(Get-Date) Rebooting the instance" } else { Write-Host " The server instance is already a member of a domain" } } -ArgumentList $admin_pwd, $DomainName, $DomainMode, $DatabasePath, $LogPath, ` $SysvolPath, $ForestMode # Remove the session object Remove-PSSession $session Remove-Variable session ################################################################################ # Wait until the instance is restarted # Notice we wait for the text 'Starting shutdown scripts' in Serial Console ################################################################################ $creation_status = Get-GceInstance -zone $zone_domain_cntr -Name $domain_cntr -SerialPortOutput | Select-String -Pattern 'Starting shutdown scripts' -Quiet while (!($creation_status)) { Write-Host "$(Get-Date) Waiting for instance $domain_cntr to restart" Start-Sleep -s 30 $creation_status = Get-GceInstance -zone $zone_domain_cntr -Name $domain_cntr -SerialPortOutput | Select-String -Pattern 'Starting shutdown scripts' -Quiet } # Wait 2 minutes to make sure all AD services are running before we continue Start-Sleep -s 120 ################################################################################ # Create a remote session to the domain controller ################################################################################ $session_options = New-PSSessionOption -SkipCACheck -SkipCNCheck ` -SkipRevocationCheck $session = New-PSSession -ComputerName $ip_address -UseSSL ` -Credential $credential -SessionOption $session_options ################################################################################ # Change the password for the domain administrator ################################################################################ Write-Host "$(Get-Date) Change password for Administrator" Invoke-Command -Session $session -ScriptBlock { param($admin_pwd) Set-ADAccountPassword ` -Identity "Administrator" ` -Reset ` -NewPassword $admin_pwd } -ArgumentList $admin_pwd # Remove the session object Remove-PSSession $session Remove-Variable session } else { Write-Host " The instance: $domain_cntr already exist" } Write-Host " " Write-Host "IMPORTANT: You are now ready to run the script: create-sql-instance-availability-group.ps1" # Remove firewall rules that allow current IP to RDP and WinRM # Uncomment if you don't want to keep those firewall rules <# if ( ( Get-GceFirewall | Where {$_.Name -eq "allow-winrm-current-ip"} ) ) { Remove-GceFirewall -FirewallName "allow-winrm-current-ip" } if ( ( Get-GceFirewall | Where {$_.Name -eq "allow-rdp-current-ip"} ) ) { Remove-GceFirewall -FirewallName "allow-rdp-current-ip" } #>