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})(?<![^:]:|\.)\z") {
                    $ConvertedOpts = $ConvertedOpts + "( $(ConvertTo-SQLString $IpOption) )"
                    continue
                }
                throw "Malformed IPv4/IPv6 address: $IpOption"
            }
            $ListenerAddr = "IP ( $($ConvertedOpts -join ', ') )"
        }
    }
    if (($ListenerDef["NAME"] -eq $null) -or ($ListenerDef["NAME"] -match "^\s*$")) {
        throw "Listener name is required"
    }
    if (-not ($ListenerDef["NAME"] -match "^[A-Za-z0-9\._\-]+$")) {
        throw "Illegal listener name. It can contain only alphanumeric characters, dashes (-), and hyphens (_), in any order."
    }
    $Port = $null
    if ($ListenerDef["PORT"] -ne $null) {
        if ($ListenerDef["PORT"] -match "\d+\+") {
            $StartingPort = $ListenerDef["PORT"] -replace "\+", ""
            $Port = Get-NextFreePort $StartingPort
            $ListenerAddr = $ListenerAddr + ", PORT = $Port"
        } else {
            if ($ListenerDef["PORT"] -match "\d+") {
                $ListenerAddr = $ListenerAddr + ", PORT = $($ListenerDef["PORT"])"
            } else {
                throw "Invalid port value: $($ListenerDef["PORT"])"
            }
        }
    }
    $Listener = "LISTENER '$($ListenerDef["NAME"])' ( WITH $ListenerAddr )"

    $Name | Out-File "$WorkDir\avgroup.name"
    
    for ($i = 0; $i -lt $DatabaseNames.Length; $i++) {
        $DataBaseName = $DatabaseNames[$i]
        $DataBaseName | Out-File "$WorkDir\db$i.name"
        New-SQLDatabase $DataBaseName
        $BackupDb = "BACKUP DATABASE $(ConvertTo-SQLName $DataBaseName) TO DISK = N$(ConvertTo-SQLString "$WorkDir\db$i.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\db${i}.log.bak") WITH NOFORMAT, INIT,  NAME = N'Transaction Log  Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10
                      GO"
        [void](Invoke-SQLText -SQL $BackupLog)
    }
    $ReplicaDefinitionsArray = @()
    if ($Prefs.Length -gt 0) {
        $PrefsLine = "WITH ( $($Prefs -join ', ') )"
    } else {
        $PrefsLine = ""
    }
    $SQL = "CREATE AVAILABILITY GROUP $(ConvertTo-SQLName $Name) $PrefsLine
                FOR DATABASE $QuotedDBNames
                REPLICA ON`r`n        $ReplicaDefinitions
                $Listener;
    "
    [void](Invoke-SQLText -SQL $SQL)
    return $Port
}

function New-AlwaysOnAvailabilityGroupReplica {
    <#
    .SYNOPSIS
    Creates AlwaysOn availability group secondary replica

    .DESCRIPTION
    Creates AlwaysOn availability group secondary replica based on information provided to and by New-AlwaysOnAvailabilityGroup.

    .PARAMETER WorkDir
    Working directory which was transferred from the primary replica.
    #>
    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
}



