functions/private/GenFuncs.ps1

function InitSqlConnection($ServerInstance, $UserName, [SecureString] $Password){
    # these sql cmd arguments will be used to splat the Invoke-SqlCmd arguments
    $SqlCmdArguments = @{
        ServerInstance = $ServerInstance
        Database = "master"
    }
    if($UserName -and $Password) {
        $SqlCmdArguments.Add("UserName", $UserName) | Out-Null
        $SqlCmdArguments.Add("Password", $Password) | Out-Null
    }

    $serverConnection = New-Object Microsoft.SqlServer.Management.Common.ServerConnection
    $serverConnection.ServerInstance = $ServerInstance
    if($UserName -and $Password) {
        $serverConnection.LoginSecure = $false
        $serverConnection.Login = $UserName
        $serverConnection.Password = $Password
    }

    $server = New-Object Microsoft.SqlServer.Management.Smo.Server($serverConnection)
    if ($null -eq $server.Version ) {
        throw "Unable to connect to: $ServerInstance"
        exit 1
    }

    return [PSCustomObject] @{
        SqlCmdArguments = $SqlCmdArguments
        Server = $server
    }
}

function GetConnectionString {
    param (
        [Parameter(Mandatory=$true)]
        [string]$ServerInstance,
        [Parameter(Mandatory=$true)]
        [string]$Database,
        [string]$UserName,
        [SecureString]$Password,
        [string]$AppName = "tcdbtools"
    )

    # in powershell you cannot use the propery names of the builder, you have to use the dictionary keys
    $builder = [System.Data.SqlClient.SqlConnectionStringBuilder]::new()
    $builder["Data Source"] = $ServerInstance
    $builder["Initial Catalog"] = $Database
    if ($AppName) {
        $builder["Application Name"] = $AppName
    }
    if ($UserName -and $Password) {
        $builder["Integrated Security"] = $false
        $builder["User ID"] = $UserName
        $builder["Password"] = $Password
    } else {
        $builder["Integrated Security"] = $true
    }
    return $builder.ConnectionString
}

function MoveIndexes ($SqlCmdArguments, $db, $fromFG, $toFG, $indicator, $timeout) {
    # using sql to scan for the indexes to move instead of scanning SMO, as SMO is very, very slow scanning the tables
    # especially if some of the tables do not have indexes in the fromFG

    $sql = "
        SELECT OBJECT_SCHEMA_NAME(i.[object_id]) AS [schema_name],
            OBJECT_NAME(i.[object_id]) AS [object_name]
            ,i.[index_id]
            ,i.[name] AS [index_name]
            ,i.[type_desc] AS [index_type]
        FROM [$($db.Name)].[sys].[indexes] i
        INNER JOIN [$($db.Name)].[sys].[filegroups] f
            ON f.[data_space_id] = i.[data_space_id]
        WHERE OBJECTPROPERTY(i.[object_id], 'IsUserTable') = 1
            AND [f].[name] = '$fromFG'
        ORDER BY OBJECT_NAME(i.[object_id])
            ,i.[index_id]"

    Write-Verbose $sql
    $indexes = Invoke-Sqlcmd @SqlCmdArguments -Query $sql -QueryTimeout $timeout

    $indexCounter = 0
    $indexCountTotal = $indexes.Count
    $activity = "MOVING ($indexCountTotal) INDEXES FROM FILEGROUP [$fromFG] TO FILEGROUP [$toFG] FOR DATABASE: [$($db.Name)]"
    Write-Information "[$($sw.Elapsed.ToString($swFormat))] $activity"

    foreach ($tbl in ($indexes | Group-Object -Property schema_name,object_name)) {
        $table = $db.Tables.Item($tbl.Group[0].object_name, $tbl.Group[0].schema_name)
        $tableName = "[$($table.Schema)].[$($table.Name)]"

        Write-Information "[$($sw.Elapsed.ToString($swFormat))] `tTABLE: $tableName $indicator"

        # the table is a heap so we have to basically create a non-unique clustered index to move it..... then drop the index
        if (-not $table.HasClusteredIndex) {
            $firstColumn = $table.Columns | Select-Object -First 1
            $indexName =  "PK_$([Guid]::NewGuid().ToString("N"))"
            $sql = "CREATE CLUSTERED INDEX $indexName ON $tableName ($($firstColumn.Name)) WITH (DATA_COMPRESSION = PAGE) ON [$toFG];
                DROP INDEX $indexName ON $tableName"


            Write-Verbose "$sql"
            Invoke-Sqlcmd @SqlCmdArguments -Query "$sql" -QueryTimeout $timeout
        }

        foreach ($index in $table.Indexes) {
            if ($index.FileGroup -ieq $fromFG) {
                $indexCounter++

                Write-Progress -Activity $activity `
                    -Status “Moving index $indexCounter of $indexCountTotal [$($index.Name)] ” `
                    -PercentComplete (([decimal]$indexCounter / [decimal]$indexCountTotal) * 100.00)

                    Write-Information "[$($sw.Elapsed.ToString($swFormat))] `t`tINDEX: [$($index.Name)] ($indexCounter of $indexCountTotal)"

                    # set the new filegroup, and the dropexisting property so the script will generate properly
                    $index.FileGroup = $toFG
                    $index.DropExistingIndex = $true
                    $sql = $index.Script()
                    Write-Verbose "$sql"
                    Invoke-Sqlcmd @SqlCmdArguments -Query "$sql" -QueryTimeout $timeout
            }
        }
    }
    Write-Progress -Activity $activity -Completed
}


# If the script has a hard time finding SMO, you can install the dbatools module and import it. Which ensures that SMO can be found.
if (Get-Module -ListAvailable -Name dbatools) {
    Write-Verbose "Importing dbatools"
    Import-Module dbatools
}

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null