PowerShell.PowerLibrary.SqlDatabaseMaintenance.psm1

#region Variables
$OutputSqlErrors = $($ErrorActionPreference -eq 'Continue' -or $ErrorActionPreference -eq 'Stop');
#endregion

#region Private Methods
FUNCTION Invoke-SqlCmdInternal
{
    [CmdletBinding()]
    param
    ( 
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Query,

        
        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
        [string]
        $Database,

        [Parameter(Mandatory = $true, Position = 2, ValueFromPipeline = $true)]
        [System.Collections.Generic.Dictionary[string, object]]
        $InvocationBoundParameters
    );

    Invoke-Sqlcmd `
    -ErrorAction $ErrorActionPreference `
    -AbortOnError:$false `
    -OutputSqlErrors:$OutputSqlErrors `
    -QueryTimeout $InvocationBoundParameters.SqlQueryTimeout `
    -Query $Query `
    -ServerInstance $InvocationBoundParameters.ServerName `
    -Username $InvocationBoundParameters.UserName `
    -Password $InvocationBoundParameters.Password `
    -Database master;
}
#endregion

#region Set-DatabaseRecoveryMode
FUNCTION Set-DatabaseRecoveryMode
{
    <#
    .Synopsis
        Sets Database Recovery Mode.
    .DESCRIPTION
        Sets Database Recovery Mode. Simple, Full or BULK_LOGGED.
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .PARAMETER RecoveryMode
        SIMPLE | FULL. Default: SIMPLE
    .EXAMPLE
        Set-DatabaseRecoveryMode -N Portal -M SIMPLE;
    .NOTES
        Query Executed: Alter Database [$DatabaseName] SET RECOVERY $RecoveryMode;"
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (    
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName,   
             
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('M')]
        [ValidateSet('SIMPLE','FULL', 'BULK_LOGGED')]
        [string]
        $RecoveryMode = 'SIMPLE'
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }

    BEGIN 
    {        
        Write-Host "Setting Database Recovery Mode Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Setting Database " -NoNewline -ForegroundColor Yellow;
                Write-Host $__ -NoNewline -ForegroundColor White;
                Write-Host " Recovery Mode to " -NoNewline -ForegroundColor Yellow;
                Write-Host $RecoveryMode -NoNewline -ForegroundColor White;
                Write-Host " ..." -ForegroundColor Yellow;
                [PSObject]$Model = @{ DatabaseName = $__; RecoveryMode = $RecoveryMode; };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        } 
    }

    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Setting Database Recovery Mode Process Ended" -ForegroundColor DarkCyan;        
    }
}
#endregion

#region Set-DatabaseAuthorization
FUNCTION Set-DatabaseAuthorization
{
    <#
    .Synopsis
        Sets Database Authorization.
    .DESCRIPTION
        Sets Database Authorization.
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .PARAMETER AuthorizationUserName
        Database Authorization User Name.
    .EXAMPLE
        Set-DatabaseAuthorization -N Portal -M SIMPLE;
    .NOTES
        Query Executed: ALTER Authorization ON database::[$DatabaseName] to [$AuthorizationUserName]
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $AuthorizationUserName
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }
    
    BEGIN
    {
        Write-Host "Setting Database Authorization Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Altering Authorization on " -NoNewline -ForegroundColor Yellow;
                Write-Host $DatabaseName -NoNewline -ForegroundColor White;
                Write-Host " to " -NoNewline -ForegroundColor Yellow;
                Write-Host $AuthorizationUserName -NoNewline -ForegroundColor White;
                Write-Host " ..." -ForegroundColor Yellow;
                [PSObject]$Model = @{ DatabaseName = $__; UserName = $AuthorizationUserName; };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        } 
    }
    
    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Setting Database Authorization Process Ended" -ForegroundColor DarkCyan;
    }
}
#endregion

#region Drop-Database
Set-Alias -Name Start-DatabaseDrop -Value Remove-Database;
Set-Alias -Name Drop-Database -Value Remove-Database;
FUNCTION Remove-Database
{
    <#
    .Synopsis
        Drops one or more database(s).
    .DESCRIPTION
        Drops one or more database(s).
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .EXAMPLE
        Start-DatabaseDrop 'Portal';
    .NOTES
        Query Executed:
        EXEC msdb.dbo.sp_delete_database_backuphistory @database_name = N'Portal'
        GO
        USE [master]
        GO
        IF (EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = 'Portal' OR name = 'Portal')))
        BEGIN
            ALTER DATABASE [Portal] SET single_user WITH ROLLBACK IMMEDIATE;
            DROP DATABASE [Portal];
        END
        GO
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }
    
    BEGIN
    {
        Write-Host "Dropping Database(s) Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Dropping $DatabaseName..." -ForegroundColor Yellow;
                [PSObject]$Model = @{ DatabaseName = $__; };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        }
    }

    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Dropping Database Process Ended" -ForegroundColor DarkCyan;
    }
}
#endregion

#region Restore-Database
Set-Alias -Name Start-DatabaseRestore -Value Restore-Database;
FUNCTION Restore-Database
{
    <#
    .Synopsis
        Drops one or more database(s), and then Restore them.
    .DESCRIPTION
        Drops one or more database(s), and then Restore them.
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .PARAMETER BackupPath
        Database Backup Path.
    .PARAMETER DataPath
        Database Data Path.
    .PARAMETER LogPath
        Database Log Path.
    .EXAMPLE
        $DatabaseName.Values | `
        Start-DatabaseRestore `
        -BackupPath "C:\Program Files\Microsoft SQL Server\Repository\Backup" `
        -DataPath "C:\Program Files\Microsoft SQL Server\Repository\Data" `
        -LogPath "C:\Program Files\Microsoft SQL Server\Repository\Log" `
        ;
    .NOTES
        Query Executed:
        USE [master]
        GO
        RESTORE DATABASE [@Model.DatabaseName]
        FROM DISK = N'@Model.BackupPath\@Model.DatabaseName.bak'
        WITH FILE = 1,
        MOVE N'@Model.DatabaseName' TO N'@Model.DataFileName',
        MOVE N'@Model.LogName' TO N'@Model.LogFileName',
        NOUNLOAD,
        REPLACE,
        STATS = 5
        GO
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $BackupPath,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $DataPath,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $LogPath
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }
    
    BEGIN
    {
        Write-Host "Restoring Database(s) Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Restoring $DatabaseName..." -ForegroundColor Yellow;
                [PSObject]$Model = @{ 
                DatabaseName = $__; 
                BackupPath = $BackupPath;
                DataFileName = "$DataPath\$__.mdf";
                LogFileName = "$LogPath\$__.ldf";
                LogName = "$($__)_log";
                };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        }
    }

    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Restoring Database Process Ended" -ForegroundColor DarkCyan;
    }  
}
#endregion

#region Compress-DatabaseLog
Set-Alias -Name Start-DatabaseLogShrink -Value Compress-DatabaseLog;
Set-Alias -Name Shrink-DatabaseLog -Value Compress-DatabaseLog;
FUNCTION Compress-DatabaseLog
{
    <#
    .Synopsis
        Drops one or more database(s), and then Restore them.
    .DESCRIPTION
        Drops one or more database(s), and then Restore them.
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .EXAMPLE
        Start-DatabaseLogShrink 'Portal';
    .NOTES
        Query Executed:
        Alter Database [@Model.DatabaseName] SET RECOVERY SIMPLE;
        GO
        USE [@Model.DatabaseName]
        GO
        DBCC SHRINKFILE (N'@Model.LogName' , 0, TRUNCATEONLY)
        GO
        Alter Database [@Model.DatabaseName] SET RECOVERY FULL;
        GO
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }

    BEGIN
    {
        Write-Host "Database Log Shrink Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Shrinking Database Log $DatabaseName..." -ForegroundColor Yellow;
                [PSObject]$Model = @{ 
                DatabaseName = $__; 
                LogName = "$($__)_log";
                };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        }
    }

    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Database Log Shrink Process Ended" -ForegroundColor DarkCyan;        
    }   
}
#endregion

#region Backup-Database
Set-Alias -Name Start-DatabaseBackup -Value Backup-Database;
FUNCTION Backup-Database
{
    <#
    .Synopsis
        Drops one or more database(s), and then Restore them.
    .DESCRIPTION
        Drops one or more database(s), and then Restore them.
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .PARAMETER BackupPath
        Database Backup Path.
    .EXAMPLE
        Start-DatabaseBackup -DatabaseName 'Portal' -BackupPath;
    .NOTES
        Query Executed:
        BACKUP DATABASE [@Model.DatabaseName] TO DISK = N'@Model.BackupPath\@Model.DatabaseName.bak' WITH NOFORMAT, INIT, NAME = N'@Model.DatabaseName-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, COMPRESSION, STATS = 10
        GO
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $BackupPath,
                
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [switch]
        $UniqueBackupName
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }
    
    BEGIN
    {
        Write-Host "Database Backup Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Backing up Database: $DatabaseName..." -ForegroundColor Yellow;
                $BackupName = $__;
                IF($UniqueBackupName.IsPresent)
                {
                    $BackupName = "$($__)-$([datetime]::Now.ToString('yyyy-MM-dd-hh-mm-ss'))";
                }
                [PSObject]$Model = @{ 
                DatabaseName = $__; 
                BackupName = $BackupName;
                BackupPath = $BackupPath;
                };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        }
    }

    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Database Backup Process Ended" -ForegroundColor DarkCyan;
    }   
}
#endregion

#region Repair-SSB
Set-Alias -Name Fix-SSB -Value Repair-SSB;
FUNCTION Repair-SSB
{
    <#
    .Synopsis
        Drops one or more database(s), and then Restore them.
    .DESCRIPTION
        Drops one or more database(s), and then Restore them.
    .PARAMETER SqlQueryTimeout
        Server Name. Default: 0
    .PARAMETER ServerName
        Server Name. Default: (local)
    .PARAMETER UserName
        SQL User Name. Default: sa
    .PARAMETER Password
        SQL User Name Password.
    .PARAMETER DatabaseName
        Database Name.
    .PARAMETER AuthorizationUserName
        Database Authorization User Name.
    .EXAMPLE
        Start-DatabaseLogShrink 'Portal';
    .NOTES
        Query Executed:
        USE [Master]
        GO
        GRANT EXTERNAL ACCESS ASSEMBLY TO [@Model.UserName]
        GO
        ALTER Authorization on Database::[@Model.DatabaseName] to [@Model.UserName];
        GO
        ALTER DATABASE [@Model.DatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
        GO
        ALTER DATABASE [@Model.DatabaseName] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;
        GO
        ALTER DATABASE [@Model.DatabaseName] SET TRUSTWORTHY ON;
        GO
        ALTER DATABASE [@Model.DatabaseName] SET NEW_BROKER WITH ROLLBACK IMMEDIATE;
        GO
        ALTER DATABASE [@Model.DatabaseName] SET MULTI_USER WITH ROLLBACK IMMEDIATE;
        GO
        sp_configure 'clr enabled';
        GO
        RECONFIGURE
        GO
        USE [@Model.DatabaseName]
        Go
        ALTER ASSEMBLY [??????.ClrProcedures] WITH PERMISSION_SET = UNSAFE;
        GO
        RECONFIGURE
        GO
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias('N')]
        [string[]]
        $DatabaseName,
        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $AuthorizationUserName
    );

    DynamicParam { return (Get-DynamicParameters "$PSScriptRoot\CommonParameters.ps1"); }

    BEGIN
    {
        Write-Host "Fix SSB Process Started" -ForegroundColor Cyan;
        $QueryBuilder = [System.Text.StringBuilder]::new();
        $TemplateFile = "$PSScriptRoot\Templates\$($MyInvocation.MyCommand.Name).sql.template" ;
        $Confirmed = $false;
    }

    PROCESS
    {
        FOREACH($__ in $DatabaseName)
        {
            IF($PSCmdlet.ShouldProcess($__) -or $WhatIfPreference.IsPresent)
            {
                $Confirmed = $true;
                Write-Host "Fixing SSB on Database $DatabaseName..." -ForegroundColor Yellow;
                [PSObject]$Model = @{ 
                DatabaseName = $__; 
                UserName = $AuthorizationUserName;
                };
                $PartialQuery = Get-FormattedTemplate -Model $(ConvertTo-Json $Model) -Path $TemplateFile -Silent:$true;
                $QueryBuilder.AppendLine($PartialQuery) | Out-Null;
            }
        }
    }

    END
    {
        $Query = $QueryBuilder.ToString();
        IF($WhatIfPreference.IsPresent) { RETURN $Query; }       
        IF($Confirmed)
        {
            Invoke-SqlCmdInternal -Query $Query -Database 'master' -InvocationBoundParameters $PsBoundParameters;
        }
        Write-Host "Fix SSB Process Ended" -ForegroundColor DarkCyan;        
    }   
}
#endregion