NewCustomAG.psm1

 #Requires -Version 3

 function New-CustomAG{
 <# .SYNOPSIS
        Creates AlwaysOn availabilty group for servers provided.
    .DESCRIPTION
        Utilizes SMO to access SQL Server to backup database and create Availabity Group Based on Params
        If SQL Client Tools are not loaded on the machine it will error and fail.
        If AlwaysOn is not enabled on the Instance of SQL Server the service will be restarted to enable it. This is required for it to continue with AG Creation.
        If you do not want the service to be recycled enable prior to executing the script.
    .PARAMETER Servers
        Servers participating in AlwaysOn, first server will be assumed to be where the database is which needs to be replicated.
    .PARAMETER SQLAgName
        Availability Group Name database will be created in
    .PARAMETER CNO
        Cluster Name Object which was created IN Active Directory. If not passed assumes its derived from server names.
    .PARAMETER AgListenerName
        Listener Name to be created that references availability group if no listener is passed it will be created same as SQLAgname
    .PARAMETER SqlAgDatabase
        Database which will be placed in Availability Group. If HADRSeed database will be created and dropped.
    .PARAMETER AgListenerPort
        Port listener will listen on
    .PARAMETER IPAddresses
        One or Many Ip addresses which will be tied to the listener. One Ip is required for each subnet
    .PARAMETER AgListenerSubnetMask
        Subnetmask for Listeners (Assumption all will utilize the same subnetmask)
    .PARAMETER BackupDirectory
        Backup location which all nodes will have access to. Used to backup and restore database from.
    .EXAMPLE
        Creates Availability Group AG-TNTNTX10 for three servers cross subnet
        New-CustomAG -Servers TNSQL5010,TNSQL5011,TXSQL5010 -SqlAgName AG-TNTNTX10 -IPAddresses 192.168.81.211,192.168.91.212 -BackupDirectory \\ohnas001\SQLBackups
    .EXAMPLE
        New-CustomAG -Servers TNSQL5010,TNSQL5011,TXSQL5010,WASQL5010 -SqlAgName AG-TNTXWA5001 -CNO CNOTNTNTX5010 -IPAddresses 192.168.81.31,192.168.91.31,192.168.71.31 -BackupDirectory \\ohnas001\SQLBackups -verbose
 #>

    [CmdletBinding()]
      Param(
      [Parameter(Mandatory=$true)]
      [string[]]$Servers,
      [Parameter(Mandatory=$true)]
      [string]$SqlAgName,
      [string]$CNO,    
      [string]$AgListenerName,
      [string]$SqlAgDatabase="HADRSeed",
      [string]$AgListenerPort="1433",
      [string[]]$IPAddresses,
      [string]$AgListenerSubnetMask ="255.255.255.0",
      [Parameter(Mandatory=$true)]
      [string]$BackupDirectory
    )

    Write-Output ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Loading SMO Assemblies ...");
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") |Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | out-null
    Write-Output ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Done Loading SMO Assemblies ...");
    Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Done Loading SMO Assemblies ...");

    $HadrEndpointName = "Hadr_Endpoint"                           
    $HadrEndpointPort = 5022 
    $LoginType = "WindowsUser"
    if (!$AgListenerName) #Listener was not passed default to AG Name
    {
        $AgListenerName = $SqlAgName
    }
    $SqlServerPrim = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $Servers[0]

    #Loop through servers Backing up Database/Tranlog and Restore on each Secondary with NoRecovery
    $loopCnt = 0
    foreach ($Server in $Servers){
       
        $SqlConn = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $Server
        Try{
            If($SqlConn.IsHadrEnabled -eq 0)
                {
                    Enable-SqlAlwaysOn -ServerInstance $Server -Force
                    Get-Service -computer $Server -DisplayName SQL*Server*MSSQL* -Exclude *Agent*|Restart-Service
                    Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Finished Enabling AlwaysOn on the nodes ...");
                }
            }
       
        catch{
                Throw "Failed Enabling AlwaysOn on the $Server"
                Exit
             }
        
        If($loopCnt -eq 0) #First Server is Primary Back up Full and TranLogs
        {
            Try{                  
                    If($SqlAgDatabase -eq "HADRSeed" -and ($SqlConn.Databases[$SqlAgDatabase].Name -eq $null))
                    {
                        $Db = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database -ArgumentList $SqlConn,"HADRSeed"
                        $db.Create()
                    }
                }
            Catch{
                    Throw "Failed create $SqlAgDatabase database."
                    Exit                    
                
                 }

            Try{
                    if ($SQLconn.Databases[$SqlAgDatabase].RecoveryModel -ne "FULL")
                    {
                        $sqlconn.Databases[$SqlAgDatabase].RecoveryModel = "Full"
                        $sqlconn.Databases[$SqlAgDatabase].Alter();
                        Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) +": Database is not in Full Recovery Mode Setting to Full...");
                    }
                }
            Catch{
                   Throw "Failed to set $SqlAgDatabase to Full Recovery."
                   Exit
                }
            
            Try{
                    $DbBackup = New-Object -typename Microsoft.SqlServer.Management.Smo.Backup
                    $DbBackup.Database = $SqlAgDatabase
                    $DbBackup.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Database
                    $DbBackup.Initialize = $true
                    $DbBackup.Devices.AddDevice("$BackupDirectory\$($SqlAgDatabase)_AgSetup_full.bak", [Microsoft.SqlServer.Management.Smo.DeviceType]::File)
                    $DbBackup.SqlBackup($SqlConn)
                    Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": DbBackup Full for $SqlAgDatabase ...");
                    $DbBackup = New-Object -typename Microsoft.SqlServer.Management.Smo.Backup
                    $DbBackup.Database = $SqlAgDatabase
                    $DbBackup.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Log
                    $DbBackup.Initialize = $true
                    $DbBackup.Devices.AddDevice("$BackupDirectory\$($SqlAgDatabase)_AgSetup_log.trn", [Microsoft.SqlServer.Management.Smo.DeviceType]::File)
                    $DbBackup.SqlBackup($SqlConn)
                    Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": DbBackup TranLog for $SqlAgDatabase ...");           
                }
            Catch{
                    Throw "Failed to backup $SqlAgDatabase"
                    Exit
                }
            

        }#if First Server Backup

        else
        {
                try{
                    $DbRestore = New-Object -typename Microsoft.SqlServer.Management.Smo.Restore
                    $DbRestore.Database = $SqlAgDatabase
                    $DbRestore.Action = [Microsoft.SqlServer.Management.Smo.RestoreActionType]::Database
                    $DbRestore.Devices.AddDevice("$BackupDirectory\$($SqlAgDatabase)_AgSetup_full.bak",
                        [Microsoft.SqlServer.Management.Smo.DeviceType]::File)
                    $DbRestore.NoRecovery = $true
                    $DbRestore.SqlRestore($SqlConn)
                    Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Restore Full for $SqlAgDatabase on $Server...");
                    $DbRestore = New-Object -typename Microsoft.SqlServer.Management.Smo.Restore
                    $DbRestore.Database = $SqlAgDatabase
                    $DbRestore.Action = [Microsoft.SqlServer.Management.Smo.RestoreActionType]::Log
                    $DbRestore.Devices.AddDevice("$BackupDirectory\$($SqlAgDatabase)_AgSetup_log.trn",
                        [Microsoft.SqlServer.Management.Smo.DeviceType]::File)
                    $DbRestore.NoRecovery = $true
                    $DbRestore.SqlRestore($SqlConn)
                    Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Restore Tran Log for $SqlAgDatabase on $Server...");      
                    }
                
                catch{
                        Throw "Failed to Restore $SqlAgDatabase on $Server..."
                        Exit
                    } 
        }#Else Secondary Servers
        $loopcnt=$loopCnt+1
    }#For Each Loop through servers Backing up Database/Tranlog
                     

    $AvailabilityGroup = New-Object -typename Microsoft.SqlServer.Management.Smo.AvailabilityGroup -ArgumentList $SqlServerPrim, $SqlAgName
    $AvailabilityGroup.AutomatedBackupPreference="Primary"

    #Loop through servers and create Endpoints for AlwaysOn
    $loopCnt = 0
    Foreach($Server in $Servers){
        Try{
                $SqlConn = New-Object -typename Microsoft.SqlServer.Management.Smo.Server -ArgumentList $Server
                $Endpoint = $SqlConn.Endpoints | Where-Object {$_.EndpointType -eq [Microsoft.SqlServer.Management.Smo.EndpointType]::DatabaseMirroring}
                $ServiceAccount = $SQLconn.ServiceAccount
            
                if(($SqlConn.logins | Where-Object {($_.LoginType -eq "WindowsUser") -and ($_.Name -eq $ServiceAccount)}) -eq $null){

                    $login = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList $SqlConn ,$ServiceAccount
                    $login.LoginType = $LoginType
                    $login.Create()
                    }

                if(!$Endpoint){

                        $Endpoint = New-Object -typename Microsoft.SqlServer.Management.Smo.Endpoint -ArgumentList $SqlConn,$HadrEndpointName
                        $Endpoint.EndpointType = [Microsoft.SqlServer.Management.Smo.EndpointType]::DatabaseMirroring
                        $Endpoint.ProtocolType = [Microsoft.SqlServer.Management.Smo.ProtocolType]::Tcp
                        $Endpoint.Protocol.Tcp.ListenerPort = $HadrEndpointPort
                        $Endpoint.Payload.DatabaseMirroring.ServerMirroringRole = [Microsoft.SqlServer.Management.Smo.ServerMirroringRole]::All
                        $Endpoint.Payload.DatabaseMirroring.EndpointEncryption = [Microsoft.SqlServer.Management.Smo.EndpointEncryption]::Required
                        $Endpoint.Payload.DatabaseMirroring.EndpointEncryptionAlgorithm = [Microsoft.SqlServer.Management.Smo.EndpointEncryptionAlgorithm]::Aes 
                        $Endpoint.Create()
                        $Endpoint.Start()
                        Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Create Endpoint on $Server...");
                        Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Granting connect Permission on $Server to $ServiceAccount ...");
                        #$ConnectPerm = New-Object Microsoft.SqlServer.Managment.Smo.ObjectPermissionSet(Connect)
                        #$Endpoint.Grant($ConnectPerm,$ServiceAccount)
                        $Cmd = "GRANT CONNECT ON ENDPOINT::[" + $HadrEndpointName + "] TO [" + $ServiceAccount + "]"
                        $con = "server=$Server;database=master;Integrated Security=True;"  
                        $da = new-object -typename System.Data.SqlClient.SqlDataAdapter -ArgumentList $Cmd, $con
                        $dt = new-object -typename System.Data.DataTable
                        $da.fill($dt) | out-null
        
                        Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Done granting connect Permission on $Server to $ServiceAccount ...");
                    }

                If($loopCnt -eq 0){

                    $PrimaryReplica = New-Object -typename Microsoft.SqlServer.Management.Smo.AvailabilityReplica -ArgumentList $AvailabilityGroup, $SqlServerPrim.NetName
                    $PrimaryReplica.EndpointUrl = "TCP://$($SqlServerPrim.NetName):$($Endpoint.Protocol.Tcp.ListenerPort)"
                    $PrimaryReplica.FailoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::Automatic
                    $PrimaryReplica.AvailabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::SynchronousCommit
                    $AvailabilityGroup.AvailabilityReplicas.Add($PrimaryReplica)
                    Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Added Primary Replica..."+$PrimaryReplica.EndpointUrl);
                    }
                Else{

                    $SecondaryReplica = New-Object -typename Microsoft.SqlServer.Management.Smo.AvailabilityReplica -ArgumentList $AvailabilityGroup, $SqlConn.NetName
                    $SecondaryReplica.EndpointUrl = "TCP://$($SqlConn.NetName):$($Endpoint.Protocol.Tcp.ListenerPort)"
                    If ($loopCnt -eq 1) 
                        {
                            $SecondaryReplica.FailoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::Automatic
                            $SecondaryReplica.AvailabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::SynchronousCommit
                        }
                    Else{
                            $SecondaryReplica.FailoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::Manual
                            $SecondaryReplica.AvailabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::ASynchronousCommit
                        }

                    $AvailabilityGroup.AvailabilityReplicas.Add($SecondaryReplica)
                    Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Added Secondary Replica..."+$SecondaryReplica.EndpointUrl)
                    }
                    }
        Catch {
            Throw "Failed to Create/Grant Endpoints on $Server..."
            Exit
        }        
        $loopCnt=$loopCnt+1 
    }#For Each Create Endpoints

    $AvailabilityDb = New-Object -typename Microsoft.SqlServer.Management.Smo.AvailabilityDatabase -ArgumentList $AvailabilityGroup, $SqlAgDatabase
    $AvailabilityGroup.AvailabilityDatabases.Add($AvailabilityDb)
    Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Added Database to AG..."+$AvailabilityDb);
    
    #if No ip Addresses Passed we will not create a listener
    if ($IpAddresses){
            #If CNO is not passed we will derive it from server names
            #First three Char ="CLU"
            #Next Series are the first two char of server name which is location ex. NY for NewYork
            #Final is the Last four digits of the Primary Server
            #For the following Servers MISQL5010, TNSQL5010, OHSQL5010 CNO would be CluMITNOH5010
            If (!$CNO){    
                    Try
                    {
                        $CNO = "Clu"
                        foreach ($Server in $Servers)
                        { 
                            $CNO = $CNO + $Server.Substring(0,2)
                        }
                        $CNO =$CNO + $Server.Substring($Server.Length - 4,4)
                        Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) +": Captured Cluster Name as $CNO...")
                    }
                    Catch
                    {
                        Throw ": Failed to Get Cluster Name"
                        exit
                    }
            }

            #Verify Active Directory Tools are installed, if they are load if not Throw Error
            If (!(get-module -ListAvailable | Where-Object {$_.Name -eq "ActiveDirectory"})){
                Throw "Active Directory Module is Required."
                Exit
            }
            else{import-module ActiveDirectory -ErrorAction Stop}
            try{
                $CNO_OU = Get-ADComputer $CNO
                $Trim = $CNO.length+4
                $CNOlgth = $CNO_OU.DistinguishedName.Length - $trim
                $OUPath = $CNO_OU.ToString().Substring($Trim,$CNOlgth)
                }
            catch{
                Throw ": Failed to find Computer in AD"
                exit
            }

            #Create Computer Object for the AgListenerName
            #Grant Full control to CNO Computer Object using DSACLS
            
                $m = Get-ADComputer -Filter {Name -eq $SqlAgName} -Server $env:USERDOMAIN | Select-Object -Property * | Measure-Object

                If ($m.Count -eq 0)
                {
                    Try{
                        New-ADComputer -Name $AgListenerName -SamAccountName $AgListenerName -Path $OUPath -Enabled $false
                        }
                    Catch{
                           Throw "Failed to Create $AgListenerName in $AG_OU"
                        Exit
                        }
                        
                        $SucccessChk =0

                    #Check for AD Object Validate at least three successful attempts
                    $i=1
                    While ($i -le 5) {
                        Try{
                            $ListChk = Get-ADComputer -filter {Name -like $AgListenerName}
                            If ($ListChk){$SuccessChk++}
                            Start-Sleep -Seconds 10  
                            If($SuccesChk -eq 3){break}
                           }
                        Catch{
                             Throw "Failed Validate $AgListenerName was created in $OUPath"
                             Exit
                        }
                        $i++
                    }            
                }
                Try{
                    $AG_OU = Get-ADComputer $AgListenerName
                    DSACLS $AG_OU /I:T /G $CNO_OU":GA" | Out-Null
                    Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) +": Assigned permisions to $SqlAgName and $CNO in $OUPath")
                    }
                Catch{
                      Throw "Failed Validate grant permissions on $AgListenerName in location $OU_PAth to $CNO in location $CNO_OU"
                      Exit
                }

    
            $AgListener = New-Object -typename Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener -ArgumentList $AvailabilityGroup, $AgListenerName
            $AgListener.PortNumber = $AgListenerPort
            Foreach($IP in $IPAddresses){
            Try{
                $AgListenerIp = New-Object -typename Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress -ArgumentList $AgListener
                $AgListenerIp.IsDHCP = $false
                $AgListenerIp.IPAddress = $IP
                $AgListenerIp.SubnetMask = $AgListenerSubnetMask
                $AgListener.AvailabilityGroupListenerIPAddresses.Add($AgListenerIp)
                Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Added $IP to $AgListenerName...");
                }
            Catch {
                Throw "Failed to Add $IP to $AgListenerName..."
                Exit
                }
            }#For Each IP Address
            
            Try{
                $AvailabilityGroup.AvailabilityGroupListeners.Add($AgListener);
                }
            Catch{
                Throw "Failed to Add $AgListenerName to $SqlAgName..."
                Exit
                }        
    }#If Ipaddresses are passed
    
    #Create Availabilty Group
    Try{ 
        $SqlServerPrim.AvailabilityGroups.Add($AvailabilityGroup)
        $AvailabilityGroup.Create()
        Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Created AG...");
        }
    Catch{

        Throw ": Failed to Create Availability Group $SqlAgName..."
        Exit
        } 
    
    #Loop through Secondary Servers and Join them to the Availability Group
    $loopCnt =0
    Foreach($Server in $Servers){
    Try {
        if ($loopCnt -ne 0){
            $SqlConn = New-Object -typename Microsoft.SqlServer.Management.Smo.Server -ArgumentList $Server
            $SqlConn.JoinAvailabilityGroup($SqlAgName)
            $SqlConn.AvailabilityGroups[$SqlAgName].AvailabilityDatabases[$SqlAgDatabase].JoinAvailablityGroup()
            Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Join Secondary $Server to AG...");
            }
        }
    Catch{
        Throw "Failed to Join $Server to $SqlAgName..."
        Exit
        } 
        $loopCnt=$loopCnt+1
    }#For Loop for Joining Secondaries

    Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Done Creating Adding Replicas...");

    #Remove all Backup Files which were creating during this excersize
    Try{
        $DBBackupFile = "$BackupDirectory\$($SqlAgDatabase)_AgSetup_full.bak"
        $DBTRNFile = "$BackupDirectory\$($SqlAgDatabase)_AgSetup_log.trn"
        If (Test-Path -Path $DBBackupFile){Remove-Item -Path $DBBackupFile}
        If (Test-Path -Path $DBTRNFile){Remove-Item -Path $DBTRNFile}
        }    Catch{
        Throw "Failed to Cleanup Backup Files..."
        Exit
        } 
    
    Write-Verbose -Message  ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Done Cleaning Up Backup Files...");

    #If we created a dummy database to create the AG Remove the database from AG and drop it
    If($SqlAgDatabase -eq "HADRSeed"){
        $loopCnt = 0
        $SqlServerPrim.AvailabilityGroups[$SqlAgName].AvailabilityDatabases[$SqlAgDatabase].Drop();
        Foreach($Server in $Servers){
        Try {
                Start-Sleep -Seconds 30; 
                $SqlConn = New-Object -typename Microsoft.SqlServer.Management.Smo.Server -ArgumentList $Server
                If($loopCnt -eq 0){$sqlconn.KillAllProcesses($SqlAgDatabase)};
                $sqlconn.Databases[$SqlAgDatabase].Drop() 
                Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": Removed $SqlAgDatabase from $Server");
           }
        Catch{
            Write-Verbose -Message  "Failed to remove $SqlAgDatabase from $Server"
            }
        $loopCnt=$loopCnt+1
        }#ForEach Server Remove Seed DB
    }#If HADRSeed
}#Function End


#New-CustomAG -Servers TNSQL5010,TNSQL5011,TXSQL5010,WASQL5010 -SqlAgName AG-TNTXWA5001 -CNO CluTNTNTX5010 -IPAddresses 192.168.81.31,192.168.91.31,192.168.71.31 -BackupDirectory \\ohnas001\SQLBackups -verbose