SqlStig.psm1

$script:LocalNames = @(".", "localhost", "127.0.0.1", "")
[System.String[]]$script:IgnoreNames = @("##*##", "NT SERVICE\*", "NT AUTHORITY\*", "sys", "INFORMATION_SCHEMA")
[System.String[]]$script:DatabaseAudits = @("SELECT", "INSERT", "UPDATE", "DELETE", "EXECUTE")
[System.String[]]$script:LogLevels = @("Always", "Never", "OnFailure", "OnSuccess")

#region Top Level Functions

Function Set-SQLInstanceStigItems {
    <#
        .SYNOPSIS
            Executes all of the SQL 2014 INSTANCE STIG settings included in the module.
 
        .DESCRIPTION
            The cmdlet runs each audit and configuration setting in the module.
 
            This cmdlet will take the following actions:
 
            1) Sets permissions on the audit files identified in the database
            2) Sets permissions and auditing on the installation location of the shared files and directories
            3) Sets permissions on the directory structure holding the SQL Instance database and log files
            4) Enables the default trace
            5) Disables xp_cmdshell
            6) Renames the built in sa account
            7) Disabled the built in sa account
            8) Creates logins and builds 2 roles with those logins as members that have permissions for Instance and Database auditing respectively
            9) Creates the required auditing for the Instance and optionally on every database
            10) Creates logins and builds 4 roles with those logins as members that supplement the built in fixed roles, sysadmin, serveradmin, and dbcreator. It also creates a
                role for the CONNECT SQL permission. Any existing logins that have the permissions assigned to these roles explicity will have those permissions revoked and moved to
                the new role. Additionally, any existing logins that are members of the built in fixed roles will be moved into the new role. The 4 roles are named SYSADMIN_ROLE, SERVERADMIN_ROLE,
                DBCREATOR_ROLE, and CONNECT_SQL_ROLE. This action ignores login principals like ##*##, NT SERVICE\*, NT AUTHORITY\*, sys, and INFORMATION_SCHEMA.
            11) Forces encrypted connections, if no certificate is defined for the SQL instance, this setting is ignored
            12) Creates a job category for STIG Audits
            13) Sets the password policy and expiration for local SQL accounts, including the sa account.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER AuditorAdministratorsName
            The name of the user or group responsible for the audit administrator role.
 
        .PARAMETER AuditorsName
            The name of the user or group responsible for auditing databases, primarily accessing audit log files.
 
        .PARAMETER EncryptConnection
            Specifies that connections made to the SQL instance are encrypted. The SQL instance must have a trusted certificate configured for this to work.
 
        .PARAMETER Version
            The version of SQL being configured, select from either 10 (SQL 2005), 11 (SQL 2008), 12 (SQL 2014), or 13 (SQL 2016). This defaults to 12. It is used to locate the correct modules in the filesystem.
 
        .PARAMETER Credential
            The credentials to use to connect to the SQL host node.
 
        .PARAMETER SQLAdministratorsName
            The Windows User or Group that is granted full control over the SQL Instance directory structure.
 
        .PARAMETER ServerAuditor
            The Windows User or Group that is granted ALTER ANY SERVER AUDIT on the SQL Instance via role membership.
 
        .PARAMETER DatabaseAuditor
            The Windows User or Group that is granted ALTER ANY DATABASE AUDIT on every database on the SQL Instance via role membership.
 
        .PARAMETER NewSAName
            The new name to assign to the built in sa account. This defaults to xsa.
 
        .PARAMETER AuditDestination
            The destination to send audits, either the ApplicationLog, SecurityLog, or File. The are special considerations when choosing the SecurityLog option, see https://msdn.microsoft.com/en-us/library/cc645889.aspx.
             
        .PARAMETER AuditFileDestinationPath
            If the AuditDestination parameter is set to File, this is the location the audit file will be written. If the AuditDestination is set to ApplicationLog or SecurityLog, this setting is ignored.
 
        .PARAMETER SysAdminRoleMembers
            The names of Windows Users or Groups or local SQL accounts that should have a new Server Login created, if it doesn't already exist, and made a member of the new SYSADMIN role.
 
        .PARAMETER ServerAdminRoleMembers
            The names of Windows Users or Groups or local SQL accounts that should have a new Server Login created, if it doesn't already exist, and made a member of the new SERVERADMIN role.
 
        .PARAMETER DBCreatorRoleMembers
            The names of Windows Users or Groups or local SQL accounts that should have a new Server Login created, if it doesn't already exist, and made a member of the new DBCREATOR role.
 
        .PARAMETER ConnectSqlRoleMembers
            The names of Windows Users or Groups or local SQL accounts that should have a new Server Login created, if it doesn't already exist, and made a member of the new CONNECTSQL role.
 
        .PARAMETER IncludeDatabaseLevelAuditing
            This parameter specifies that all INSERT, UPDATE, SELECT, DELETE, and EXECUTE actions on every database except the master are audited. Use with caution since this will fill up the logs quickly.
 
        .PARAMETER IsManagedSQL
            This parameter specifies that the SQL Instance being STIG'd is a managed service, so the configurations at the OS level, such as file and folder permissions and auditing will not be applied.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Set-SQLInstanceStigItems -SqlAdministratorsName "Contoso\UG-SQL-Admins" `
                        -AuditorAdministratorsName "Contoso\UG-SQL-Audit-Admins" `
                        -AuditorsName "Contoso\UG-SQL-Auditors" `
                        -ServerAuditor "Contoso\UG-SQL-ServerAuditors" `
                        -DatabaseAuditor "Contoso\UG-SQL-DatabaseAuditors" `
                        -ComputerName "SQL2014" `
                        -AuditDestination "ApplicationLog" `
                        -SysAdminRoleMembers @("Contoso\UG-SQL-DEV_001-SysAdmins") `
                        -ServerAdminRoleMembers @("Contoso\UG-SQL-DEV_001-ServerAdmins") `
                        -DBCreatorRoleMembers @("Contoso\UG-SQL-DEV_001-DBCreators") `
                        -Verbose
 
            Configures all of the settings in this module.
 
        .NOTES
            This cmdlet must be run with sysadmin permissions on the SQL instance and local admin access on the node or server supporting the instance.
             
            AUTHOR: Michael Haken
            LAST UPDATE: 5/10/2016
 
        .FUNCTIONALITY
 
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-011300
                SQL4-00-030400
                SQL4-00-011900
                SQL4-00-012000
                SQL4-00-012100
                SQL4-00-012200
                SQL4-00-012300
                SQL4-00-012400
                SQL4-00-030600
                SQL4-00-034000
                SQL4-00-035600
                SQL4-00-037500
                SQL4-00-037600
                SQL4-00-037700
                SQL4-00-037800
                SQL4-00-037900
                SQL4-00-038000
                SQL4-00-017100
                SQL4-00-010200
                SQL4-00-017200
                SQL4-00-018700
                SQL4-00-020500
                SQL4-00-032500
                SQL4-00-033900
                SQL4-00-038900
                SQL4-00-013600
                SQL4-00-013700
                SQL4-00-013800
                SQL4-00-015350
                SQL4-00-031400
                SQL4-00-017100
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true)]
        [System.String]$SQLAdministratorsName,
        [Parameter(Position=1, Mandatory=$true)]
        [System.String]$AuditorAdministratorsName,
        [Parameter(Position=2, Mandatory=$true)]
        [System.String]$AuditorsName,
        [Parameter(Mandatory=$true)]
        [System.String]$ServerAuditor,
        [Parameter(Mandatory=$true)]
        [System.String]$DatabaseAuditor,
        [Parameter(Position=3)]
        [System.String]$ComputerName = "localhost",
        [Parameter(Position=4)]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(Position=5)]
        [switch]$EncryptConnection,
        [Parameter(Position=6)]
        [ValidateSet(10,11,12,13)]
        [System.Int32]$Version = 12,
        [Parameter()]
        [System.String]$NewSAName = "xsa",
        [Parameter()]
        [ValidateSet("ApplicationLog","SecurityLog","File")]
        [System.String]$AuditDestination,
        [Parameter()]
        [System.String]$AuditFileDestinationPath = [System.String]::Empty,
        [Parameter()]
        [System.String[]]$SysAdminRoleMembers,
        [Parameter()]
        [System.String[]]$ServerAdminRoleMembers,
        [Parameter()]
        [System.String[]]$DBCreatorRoleMembers,
        [Parameter()]
        [System.String[]]$ConnectSqlRoleMembers,
        [Parameter()]
        [switch]$IncludeDatabaseLevelAuditing,
        [Parameter()]
        [switch]$IsManagedSQL,
        [Parameter()] 
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty  
    )

    Begin {
        Import-SqlModule -Version $Version
    }

    Process {
        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ([System.String]::IsNullOrEmpty($NewSAName)) {
            $NewSAName = "xsa"
        }

        $SqlServer = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName

        if(!$IsManagedSQL) {
            Write-Verbose -Message "The instance is not Managed SQL, implementing OS level configurations."
            Write-Host "Setting audit file permissions."
            Set-SQLInstanceAuditFilePermissions -ComputerName $ComputerName -InstanceName $InstanceName -AuditorsName $AuditorsName -AuditorAdministratorsName $AuditorAdministratorsName

            Write-Host "Setting installation directory permissions and auditing."
            Set-SQLInstanceInstallationFilesPermissionsAndAuditing -ComputerName $ComputerName -InstanceName $InstanceName -InheritDefault
        
            Write-Host "Setting database directory and file permissions."
            Set-SQLInstanceDatabaseFilePermissions -ComputerName $ComputerName -InstanceName $InstanceName -SQLAdministratorsName $SQLAdministratorsName -IncludeLocalAdministrators

            Write-Host "Setting force encryption."
            Set-SQLInstanceForceEncryption -ComputerName $ComputerName -InstanceName $InstanceName -Enable $true -Credential $Credential
        }
        else {
            Write-Verbose -Message "The instance is Managed SQL, not implementing OS level configurations."
        }

        Write-Host "Setting default trace."
        Set-SQLInstanceDefaultTrace -SqlServer $SqlServer -Enabled $true

        Write-Host "Setting xp_cmdshell."
        Set-SQLInstanceXPCmdShell -SqlServer $SqlServer -Enabled $false

        Write-Host "Renaming sa account to $NewSAName."
        Rename-SQLInstanceAccount -SqlServer $SqlServer -Sid 0x01 -NewName $NewSAName

        Write-Host "Disabling sa account."
        Disable-SQLInstanceAccount -SqlServer $SqlServer -Sid 0x01 

        Write-Host "Setting auditors."
        Set-SQLInstanceAuditors -SqlServer $SqlServer -ServerAuditors $ServerAuditor -DatabaseAuditors $DatabaseAuditor

        Write-Host "Setting auditing."
        Set-SQLInstanceAuditing -SqlServer $SqlServer -AuditName "STIG_Audit" -AuditSpecificationName "STIG_Audit_Specification" -AuditDestination $AuditDestination -FilePath $AuditDestinationPath -IncludeDatabaseLevelAudting:$IncludeDatabaseLevelAuditing

        Write-Host "Setting management roles and logins."
        Set-SQLInstanceManagementRoles -SqlServer $SqlServer -ConnectSqlRoleMembers $ConnectSqlRoleMembers -SysAdminRoleMembers $SysAdminRoleMembers -ServerAdminRoleMembers $ServerAdminRoleMembers -DBCreatorRoleMembers $DBCreatorRoleMembers -DefaultSysAdmins $SQLAdministratorsName

        Write-Host "Creating Job Category"
        New-SQLInstanceJobCategory -SqlServer $SqlServer -Name "STIG Audits"

        Write-Host "Setting password polices."
        Set-SQLInstanceLoginPasswordPolicies -SqlServer $SqlServer -IncludeSA
    }

    End {
        Pop-Location
        Write-Host "Completed STIG." -ForegroundColor Green
    }
}

Function Set-SQLDatabaseStigItems {
    <#
        .SYNOPSIS
            Executes all of the SQL 2014 DATABASE STIG settings included in the module.
 
        .DESCRIPTION
            This cmdlet assumes there is a Mail Profile and at least 1 Operator set up with email addresses to utilize for sending notifications from the jobs that are created.
 
            This cmdlet will take the following actions:
     
            1) Creates a job to review the default trace file for CREATE, ALTER, and DROP commands targetted at functions, the job will email the results of the query
            2) Creates a job to review the default trace file for CREATE, ALTER, and DROP commands targetted at triggers
            3) Creates a job to review the default trace file for CREATE, ALTER, and DROP commands targetted at stored procedures
            4) If the database has an owner in a built in role, and the database is set to trustworthy, disables the trustworthy setting
            5) Optionally creates a Database DDL trigger to proactively notify administrators when CREATE, ALTER, and DROP commands are issued for functions, triggers, and stored procedures
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER DatabaseName
            The name of the database to run the commands against. In the case of the nightly jobs, these are created to run against every database and will not be recreated each time the cmdlet is run.
 
        .PARAMETER AllDatabases
            This parameter specifies that the trustworthy setting and Database DDL Trigger (if selected) should be run against every database.
 
        .PARAMETER OperatorToEmail
            The Operator name who has an email address configured. The email property is extracted from the Operator object and used with the sp_send_dbmail procedure to send HTML formatted emails.
            The job results and DDL Trigger notifications are sent to these email addresses.
 
        .PARAMETER JobCategory
            The Job Category name to create the 3 jobs in. The defaults to "STIG Audits".
 
        .PARAMETER AlterFunctionJobName
            The name to use when the job to monitor function modifications is created. This defaults to Alter_Function_Audit. This name is also used to name the Job Step.
         
        .PARAMETER AlterTriggerJobName
            The name to use when the job to monitor trigger modifications is created. This defaults to Alter_Trigger_Audit. This name is also used to name the Job Step.
         
        .PARAMETER AlterXPJobName
            The name to use when the job to monitor stored procedure modifications is created. This defaults to Alter_XP_Audit. This name is also used to name the Job Step.
 
        .PARAMETER JobTime
            A TimeSpan object indicating the time of day the jobs should be run. This defaults to 0200.
 
        .PARAMETER MailProfile
            The mail profile to use when sending database mail.
 
        .PARAMETER CreateAuditTrigger
            This parameter specifies that Database DDL Triggers should be created to proactively notify administrators when CREATE, ALTER, or DROP statements are executed against functions, triggers, and stored procedures.
 
            This is a much more reliable way to accomplish the intent of the scheduled jobs, but does not explicitly meet the STIG requirements. Results of the trigger are sent via HTML email.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Set-SQLDatabaseStigItems -ComputerName "SQL2014" `
                            -AllDatabases `
                            -OperatorToEmail "Database Administrators" `
                            -MailProfile "MSSQLSERVER Profile" `
                            -Verbose
 
            Implements the database STIG against all databases in the MSSQLSERVER instance on the SQL2014 server.
 
        .EXAMPLE
            Set-SQLDatabaseStigItems -ComputerName "SQL2014" `
                            -InstanceName "MyInstance" `
                            -DatabaseName "msdb" `
                            -OperatorToEmail "Database Administrators" `
                            -MailProfile "MSSQLSERVER Profile" `
                            -Verbose
 
            Implements the database STIG against the msdb database on the MyInstance instance on the SQL2014 server.
 
        .EXAMPLE
            Set-SQLDatabaseStigItems -ComputerName "SQL2014" `
                            -AllDatabases `
                            -OperatorToEmail "Database Administrators" `
                            -MailProfile "MSSQLSERVER Profile" `
                            -CreateAuditTrigger `
                            -Verbose
 
            Implements the database STIG against all databases in the MSSQLSERVER instance on the SQL2014 server and creates the Database DDL Trigger on all databases.
 
        .NOTES
            This cmdlet must be run with sysadmin permissions on the SQL instance and local admin access on the node or server supporting the instance.
             
            AUTHOR: Michael Haken
            LAST UPDATE: 5/10/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-014900
                SQL4-00-015100
                SQL4-00-015200
                SQL4-00-015610
                SQL4-00-015620
    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [System.String]$ComputerName = "localhost",
        [Parameter()]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(Mandatory=$true, Position=0, ParameterSetName="DB")]
        [System.String]$DatabaseName,
        [Parameter(Mandatory=$true, ParameterSetName="All")]
        [switch]$AllDatabases,
        [Parameter(Mandatory=$true)]
        [System.String]$OperatorToEmail,
        [Parameter()]
        [System.String]$JobCategory = "STIG Audits",
        [Parameter()]
        [System.String]$AlterFunctionJobName = "Alter_Function_Audit",
        [Parameter()]
        [System.String]$AlterTriggerJobName = "Alter_Trigger_Audit",
        [Parameter()]
        [System.String]$AlterXPJobName = "Alter_XP_Audit",
        [Parameter()]
        [System.TimeSpan]$JobTime = (New-Object -TypeName System.TimeSpan(2, 0, 0)),
        [Parameter(Mandatory=$true)]
        [System.String]$MailProfile,
        [Parameter()]
        [switch]$CreateAuditTrigger
    )

    Begin {
        if ([System.String]::IsNullOrEmpty($AlterFunctionJobName)) {
            $AlterFunctionJobName = "Alter_Function_Audit"
        }

        if ([System.String]::IsNullOrEmpty($AlterTriggerJobName)) {
            $AlterTriggerJobName = "Alter_Trigger_Audit"
        }

        if ([System.String]::IsNullOrEmpty($AlterXPJobName)) {
            $AlterXPJobName = "Alter_XP_Audit"
        }

        if ($JobTime -in @($null, [System.TimeSpan]::Zero, [System.TimeSpan]::MaxValue, [System.TimeSpan]::MinValue)) {
            $JobTime = New-Object -TypeName System.TimeSpan(2, 0, 0)
        }
    }

    Process {
        $SqlServer = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName

        [Microsoft.SqlServer.Management.Smo.Database[]]$DBs = @()

        if ($AllDatabases) {
            $DBs += $SqlConnection.Databases
        }
        else {
            $DBs += ($SqlConnection.Databases | Where-Object {$_.Name -ieq $DatabaseName} | Select-Object -First 1)
        }

        #region STIG ID: SQL4-00-014900

        if (($SqlServer.JobServer.Jobs | Where-Object {$_.Name -ieq $AlterFunctionJobName }) -eq $null) {
            Write-Verbose -Message "Creating function audit command text."
            $FunctionAuditCommandText = Get-SQLInstanceAuditCommandText -DaysDiff 1 `
                                            -JobName $AlterFunctionJobName `
                                            -OperatorToEmail $OperatorToEmail `
                                            -EmailProfile $MailProfile `
                                            -EmailSubject " - *Alert* FUNCTION Modification Detected" `
                                            -ObjectToAudit Function `

            Write-Verbose -Message "Creating function audit job."
            $FunctionCheckJob = New-SQLAgentJob -SqlServer $SqlServer `
                -Name $AlterFunctionJobName `
                -Description "Daily audit to make sure no modifications to functions have been made." `
                -EmailLevel Always `
                -EventLogLevel OnFailure `
                -NetSendLevel Never `
                -PageLevel Never `
                -DeleteLevel Never `
                -OperatorToEmail $OperatorToEmail `
                -JobCategory $JobCategory 
        
            Write-Verbose -Message "Creating function audit job step."
            $FunctionCheckJobStep = New-SQLAgentJobStep -SqlServer $SqlServer `
                -Name $AlterFunctionJobName `
                -Job $FunctionCheckJob `
                -DatabaseName "master" `
                -OnFailAction QuitWithFailure `
                -OnSuccessAction QuitWithSuccess `
                -RetryAttempts 0 `
                -RetryInterval 0 `
                -OSRunPriority 0 `
                -SubSystem TransactSql `
                -Command $FunctionAuditCommandText

            Write-Verbose -Message "Creating function audit job schedule."
            $FunctionCheckSchedule = New-SQLAgentJobSchedule -SqlServer $SqlServer `
                -Name "Daily" `
                -Job $FunctionCheckJob `
                -ActiveStartTimeOfDay $JobTime `
                -FrequencyType Daily `
                -FrequencyInterval 1 
        }
        else {
            Write-Verbose -Message "$AlterFunctionJobName job already exists."
        }

        #endregion

        #region STIG ID: SQL4-00-015100

        if (($SqlServer.JobServer.Jobs | Where-Object {$_.Name -ieq $AlterTriggerJobName }) -eq $null) {
            Write-Verbose -Message "Creating trigger audit command text."
            $TriggerAuditCommandText = Get-SQLInstanceAuditCommandText -DaysDiff 1 `
                                            -JobName "Alter_Trigger_Audit" `
                                            -OperatorToEmail $OperatorToEmail `
                                            -EmailProfile $MailProfile `
                                            -EmailSubject " - *Alert* TRIGGER Modification Detected" `
                                            -ObjectToAudit Trigger `

            Write-Verbose -Message "Creating trigger audit job."
            $TriggerCheckJob = New-SQLAgentJob -SqlServer $SqlServer `
                -Name "Alter_Trigger_Audit" `
                -Description "Daily audit to make sure no modifications to triggers have been made." `
                -EmailLevel Always `
                -EventLogLevel OnFailure `
                -NetSendLevel Never `
                -PageLevel Never `
                -DeleteLevel Never `
                -OperatorToEmail $OperatorToEmail `
                -JobCategory $JobCategory 
        
            Write-Verbose -Message "Creating trigger audit job step."
            $TriggerCheckJobStep = New-SQLAgentJobStep -SqlServer $SqlServer `
                -Name $AlterTriggerJobName `
                -Job $TriggerCheckJob `
                -DatabaseName "master" `
                -OnFailAction QuitWithFailure `
                -OnSuccessAction QuitWithSuccess `
                -RetryAttempts 0 `
                -RetryInterval 0 `
                -OSRunPriority 0 `
                -SubSystem TransactSql `
                -Command $TriggerAuditCommandText

            Write-Verbose -Message "Creating trigger audit job schedule."
            $TriggerCheckSchedule = New-SQLAgentJobSchedule -SqlServer $SqlServer `
                -Name "Daily" `
                -Job $TriggerCheckJob `
                -ActiveStartTimeOfDay $JobTime `
                -FrequencyType Daily `
                -FrequencyInterval 1 
        }
        else {
            Write-Verbose -Message "$AlterTriggerJobName job already exists."
        }

        #endregion

        #region STIG ID: SQL4-00-015200

        if (($SqlServer.JobServer.Jobs | Where-Object {$_.Name -ieq $AlterXPJobName}) -eq $null) {

            Write-Verbose -Message "Creating stored procedure audit command text."
            $XPAuditCommandText = Get-SQLInstanceAuditCommandText -DaysDiff 1 `
                                            -JobName "Alter_XP_Audit" `
                                            -OperatorToEmail $OperatorToEmail `
                                            -EmailProfile $MailProfile `
                                            -EmailSubject " - *Alert* STORED PROCEDURE Modification Detected" `
                                            -ObjectToAudit Trigger `

            Write-Verbose -Message "Creating stored procedure audit job."
            $XPCheckJob = New-SQLAgentJob -SqlServer $SqlServer `
                -Name "Alter_XP_Audit" `
                -Description "Daily audit to make sure no modifications to stored procedures have been made." `
                -EmailLevel Always `
                -EventLogLevel OnFailure `
                -NetSendLevel Never `
                -PageLevel Never `
                -DeleteLevel Never `
                -OperatorToEmail $OperatorToEmail `
                -JobCategory $JobCategory 
        
            Write-Verbose -Message "Creating stored procedure audit job step."
            $XPCheckJobStep = New-SQLAgentJobStep -SqlServer $SqlServer `
                -Name $AlterXPJobName `
                -Job $XPCheckJob `
                -DatabaseName "master" `
                -OnFailAction QuitWithFailure `
                -OnSuccessAction QuitWithSuccess `
                -RetryAttempts 0 `
                -RetryInterval 0 `
                -OSRunPriority 0 `
                -SubSystem TransactSql `
                -Command $XPAuditCommandText

            Write-Verbose -Message "Creating stored procedure audit job schedule."
            $XPCheckSchedule = New-SQLAgentJobSchedule -SqlServer $SqlServer `
                -Name "Daily" `
                -Job $XPCheckJob `
                -ActiveStartTimeOfDay $JobTime `
                -FrequencyType Daily `
                -FrequencyInterval 1 
        }
        else {
            Write-Verbose -Message "$AlterXPJobName job already exists."
        }

        #endregion

        #region STIG ID: SQL4-00-015610, SQL4-00-015620

        foreach ($Database in ($DBs | Where-Object {$_.Name -ne "msdb"})) {
            [Microsoft.SqlServer.Management.Smo.ServerRole[]]$Roles = Get-SQLInstanceServerRoleMembership -SqlServer $SqlConnection -PrincipalName $Database.Owner -OnlyFixedRoles

            if ($Database.Trustworthy -eq $true -and $Roles.Count -gt 0) {
                Write-Verbose -Message "Database $($Database.Name) is set to Trustworthy and has an owner, $($Database.Owner), that is a member of a fixed role: $($Roles -join `",`")."
                Set-SQLDatabaseTrustworthy -SqlServer $SqlConnection -DatabaseName $Database.Name -Enabled $false
            }
        }

        #endregion

        if ($CreateAuditTrigger) {
            [Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet]$EventSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet
            $EventSet.CreateProcedure = $true
            $EventSet.AlterProcedure = $true
            $EventSet.DropProcedure = $true
            $EventSet.CreateFunction = $true
            $EventSet.AlterFunction = $true
            $EventSet.DropFunction = $true
            $EventSet.CreateTrigger = $true
            $EventSet.AlterTrigger = $true
            $EventSet.DropTrigger = $true

            foreach ($Database in $DBs) {
                $Command = Get-SQLDatabaseDdlTriggerCommandText -OperatorToEmail $OperatorToEmail -Subject " $($Database.Name) - *ALERT* Object Modification Detected" -EmailProfile $MailProfile
                $NewTrigger = New-SQLDatabaseDDLTrigger -SqlServer $SqlConnection -Name "AuditModificationsTrigger" -DatabaseName $Database.Name -CommandText $Command -EventSet $EventSet -ImplementationType TransactSql
            }
        }
    }

    End {
        Write-Host "Completed STIG implementation." -ForegroundColor Green
    }
}

#endregion

Function Set-SQLInstanceForceEncryption {
    <#
        .SYNOPSIS
            Sets the force encryption registry key.
 
        .DESCRIPTION
            The cmdlet sets the force encryption registry key for the SQL Instance. If the Instance does not have a certificate configured and the cmdlet is enabling encryption, the setting does not get applied.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER EncryptConnection
            Specifies that connections made to the SQL instance are encrypted. The SQL instance must have a trusted certificate configured for this to work.
 
        .PARAMETER Enabled
            Specifies whether force encryption should be enabled or not. Defaults to $true.
 
        .PARAMETER Credential
            The credentials to use to connect to the SQL host node.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or System.Management.Automation.PSCustomObject
 
        .EXAMPLE
            Set-SQLInstanceForceEncryption -ComputerName "SQL2014" -InstanceName "MyInstance" -Enabled $true
 
            Sets the force encryption key to true.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-018700
            Rule ID
                SQL4-00-018700_rule
            Vuln ID
                SQL4-00-018700
            Severity
                CAT I
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer,
        [Parameter()]
        [bool]$Enable = $true,
        [Parameter()]
        [switch]$EncryptConnection,
        [Parameter()] 
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter()]
        $PassThru 
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
        
        Write-Verbose -Message "Setting Force Encryption."
        Write-Verbose -Message "STIG ID: SQL4-00-018700"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        Write-Verbose -Message "Getting Instance version."
        $Version = Get-SQLInstanceVersion -SqlServer $SqlConnection -ShortVersionOnly

        if ($ComputerName -iin $script:LocalNames) {
            Write-Verbose -Message "Running command locally."
            if ($Enable -eq $true ) {
                if (![System.String]::IsNullOrEmpty((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$Version.$InstanceName\MSSQLServer\SuperSocketNetLib" -Name "Certificate" | Select-Object -ExpandProperty "Certificate"))) {
                    $Property = Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$Version.$InstanceName\MSSQLServer\SuperSocketNetLib" -Name "ForceEncryption" -Value $Enable -PassThru:$PassThru
                }
                else {
                    Write-Verbose -Message "A certificate is not defined, will not force encryption."
                }
            }
            else {
                $Property = Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$Version.$InstanceName\MSSQLServer\SuperSocketNetLib" -Name "ForceEncryption" -Value $Enable -PassThru:$PassThru
            }
        }
        else {
            Write-Verbose -Message "Running command remotely."
            $Session = New-PSSession -ComputerName $ComputerName -Credential $Credential
            $Property = Invoke-Command -Session $Session -ScriptBlock { 
                if ($args[2] -eq $true) {
                    if (![System.String]::IsNullOrEmpty((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$($args[0]).$($args[1])\MSSQLServer\SuperSocketNetLib" -Name "Certificate" | Select-Object -ExpandProperty "Certificate"))) {
                        Write-Output (Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$($args[0]).$($args[1])\MSSQLServer\SuperSocketNetLib" -Name "ForceEncryption" -Value $args[2] -PassThru:$args[3])
                    }
                    else {
                        Write-Verbose -Message "A certificate is not defined, will not force encryption."
                    }
                }
                else {
                    Write-Output (Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$($args[0]).$($args[1])\MSSQLServer\SuperSocketNetLib" -Name "ForceEncryption" -Value $args[2] -PassThru:$args[3])
                }
            } -ArgumentList @($Version, $InstanceName, $Enable, $PassThru) 
                
            Remove-PSSession -Session $Session
        }
    }

    End {
        if ($PassThru) {
            Write-Output $Property
        }
    }
}

#region Smo Functions

Function Set-SQLInstanceDatabaseFilePermissions {
    <#
        .SYNOPSIS
            Access to database files must be limited to relevant processes and to authorized, administrative users.
 
        .DESCRIPTION
            The Set-DatabaseFilePermissions cmdlet sets the required security permissions for the SQL database directories and files.
 
        .PARAMETER SQLAdministratorsName
            The user or group specified as the sysadmin for the SQL instance.
 
        PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER IncludeLocalAdministrators
            Specifies that the local administrators group of the server maintaining the database shared storage should receive full control permissions.
 
        .EXAMPLE
            Set-SQLInstanceDatabaseFilePermissions -SQLAdministratorsName "UG-SQL-Admins"
             
            Configures the required permissions for the database files and directories on the MSSQLSERVER instance on the localhost.
 
        .EXAMPLE
            Set-SQLInstanceDatabaseFilePermissions -SQLAdministratorsName "UG-SQL-Admins" -ComputerName "Server01" -InstanceName "MyInstance" -IncludeLocalAdministrators
 
            Configures the required permissions for the database files and directories on the MyInstance instance on Server01 and includes the local administrators group in those permissions.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
 
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-031400
            Rule ID
                SQL4-00-031400_rule
            Vuln ID
                SQL4-00-031400
            Severity
                CAT II
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        [string]$SQLAdministratorsName,
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer,
        [Parameter()]
        [switch]$IncludeLocalAdministrators
    )

    Begin{
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
        
        Write-Verbose -Message "Setting Database File and Directory Permissions."
        Write-Verbose -Message "STIG ID: SQL4-00-031400"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        $Offset = 0
        Write-Verbose -Message "Getting service account SID."
        $ServiceAccount = $SqlConnection.Logins | Where-Object {$_.Name -eq $SqlConnection.ServiceAccount} | Select-Object -First 1         
        $ServiceAccountSid = New-Object System.Security.Principal.SecurityIdentifier($ServiceAccount.Sid, $Offset)

        Write-Verbose -Message "Getting SQL Agent service account SID."
        $SqlAgentAccount = $SqlConnection.Logins | Where-Object {$_.Name -eq $SqlConnection.JobServer.ServiceAccount} | Select-Object -First 1

        if ($SqlAgentAccount -eq $null) {
            Write-Verbose -Message "The SQL Agent service account returned null, getting the account through a query."
            $SqlAgentAccount = ($SqlConnection.Databases | Where-Object {$_.Name -eq "master"} | Select-Object -First 1).ExecuteWithResults("SELECT Sid FROM master.sys.dm_server_services svc JOIN master.sys.server_principals prin ON svc.service_account = prin.name WHERE svc.servicename LIKE 'SQL Server Agent%'").Tables[0] 
        }

        $SqlAgentAccountSid = New-Object System.Security.Principal.SecurityIdentifier([System.Byte[]]$SqlAgentAccount.Sid, $Offset)
        
        Write-Verbose -Message "Getting $SQLAdministratorsName SID."                
        $SQLAdministratorsSid = Get-AccountSid -UserName $SQLAdministratorsName    

        Write-Verbose -Message "Creating access rules."
        $AccessRules = New-SQLInstanceDatabaseDirectoryAccessRuleSet -SqlServiceAccountSid $ServiceAccountSid -SqlAgentSid $SqlAgentAccountSid -AdministratorsSid $SQLAdministratorsSid -IncludeLocalAdministrators
        
        Write-Verbose -Message "Getting data directories."
        $DataDir = $SqlConnection.DefaultFile
        $LogDir = $SqlConnection.DefaultLog
        $BackupDir = $SqlConnection.BackupDirectory
        $RootDir = $SqlConnection.RootDirectory
        $TempDBDir = $SqlConnection.Databases | Where-Object {$_.Name -eq "tempdb"} | Select-Object -First 1 | Select-Object -ExpandProperty PrimaryFilePath

        $DataDirectories = @($DataDir, $LogDir, $BackupDir, $RootDir, $TempDBDir)
        $DataDirectories = $DataDirectories | Select-Object -Unique

        Write-Verbose -Message "Removing child directories from the list of directories."
        $FinalDirs = @()

        foreach ($Dir in $DataDirectories) {
            $ShouldAdd = $true
            foreach ($OtherDir in ($DataDirectories | Where-Object {$_ -ne $Dir })) {
                if ($Dir.StartsWith($OtherDir, [System.StringComparison]::OrdinalIgnoreCase)) {
                    $ShouldAdd = $false
                    break
                }
            }
            if ($ShouldAdd) {
                Write-Verbose -Message "Unique parent path $Dir."
                $FinalDirs += $Dir
            }
            else {
                Write-Verbose -Message "Removing $Dir, it is a child of another data directory."
            }
        }

        #$DataDirectories = Get-SQLInstanceDataDirectories -InstanceName $InstanceName -ComputerName $ComputerName -EncryptConnection:$EncryptConnection -Credential $Credential
        
        Write-Verbose -Message "Transforming directories."
        $ComputerName = $SqlConnection.Name
        foreach ($Directory in $FinalDirs) {
            if (!$Directory.StartsWith("\\") -and $ComputerName -notin $script:LocalNames) {
                $Directory = $Directory.Replace(":","`$")
                $Directory = "\\$ComputerName\$Directory"
            }

            Set-FilePermissions -Path $Directory -Rules $AccessRules -Replace -ForceInheritance
        }

        <#Get-Member -InputObject $DataDirectories -MemberType Properties | ForEach-Object {
            $Path = $DataDirectories."$($_.Name)"
 
            if (!$Path.StartsWith("\\") -and $ComputerName -notin $script:LocalNames) {
                $Path = $Path.Replace(":","`$")
                $Path = "\\$ComputerName\$Path"
            }
             
            Set-FilePermissions -Path $Path -Rules $AccessRules -Replace -ForceInheritance
        }#>

    }

    End {
        Write-Verbose -Message "Completed setting database folder and file permissions."
    }
}

Function Set-SQLInstanceInstallationFilesPermissionsAndAuditing{
    <#
        .SYNOPSIS
            Software, applications, and configuration files that are part of, or related to, the SQL Server installation must be monitored to discover unauthorized changes.
 
        .DESCRIPTION
            The Set-SQLInstanceInstallationFilesPermissionsAndAudting cmdlet sets the required security permissions for the SQL installation and auditing.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER InheritDefault
            Alternatively reset inherited permissions from the parent directory, this is typically sufficient if the installation directory was set under $env:ProgramFiles
 
        .PARAMETER Replace
            Replace all permissions of the installation directory, subfolders, and files instead of re-inheriting default permissions.
 
        .EXAMPLE
            Set-SQLInstanceInstallationFilesPermissionsAndAuditing -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Configures the required permissions for the audit logs by forcing inheriting the default permissions.
 
        .EXAMPLE
            Set-SQLInstanceInstallationFilesPermissionsAndAuditing -ComputerName "SQL2014" -InstanceName "MyInstance" -Replace
             
            Configures the required permissions for the audit logs by replacing existing permissions with a new permission set.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-015350
            Rule ID
                SQL4-00-015350_rule
            Vuln ID
                SQL4-00-015350
            Severity
                CAT I
    #>


    [CmdletBinding(DefaultParameterSetName = "ExistingConnectionWithInherit")]
    Param(
        [Parameter(ParameterSetName="NewConnectionWithReplace")]
        [Parameter(ParameterSetName="NewConnectionWithInherit")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnectionWithReplace")]
        [Parameter(ParameterSetName="NewConnectionWithInherit")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnectionWithReplace",Mandatory=$true)]
        [Parameter(ParameterSetName="ExistingConnectionWithInherit",Mandatory=$true)]
        $SqlServer,
        [Parameter(ParameterSetName="NewConnectionWithReplace", Mandatory=$true)]
        [Parameter(ParameterSetName="ExistingConnectionWithReplace",Mandatory=$true)]
        [switch]$Replace,
        [Parameter(ParameterSetName="NewConnectionWithInherit")]
        [Parameter(ParameterSetName="ExistingConnectionWithInherit")]
        [switch]$InheritDefault
    )

    Begin {
        Import-SqlModule -LoadSMO
        
        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
        
        Write-Verbose -Message "Setting Installation Directory Permissions."
        Write-Verbose -Message "STIG ID: SQL4-00-015350"       
    }

    Process
    {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        #$Path = (Get-SQLInstanceDetails -ComputerName $ComputerName -InstanceName $InstanceName -EncryptConnection:$EncryptConnection -Credential $Credential).InstallationPath

        Write-Verbose -Message "Getting installation directory."
        $Path = [System.IO.Directory]::GetParent($SqlConnection.InstallDataDirectory) | Select-Object -ExpandProperty FullName

        Write-Verbose -Message "Transforming path."
        $ComputerName = $SqlConnection.Name
        if (!$Path.StartsWith("\\") -and $ComputerName -notin $script:LocalNames) {
            $Path = $Path.Replace(":","`$")
            $Path = "\\$ComputerName\$Path"
        }
        
        switch -wildcard ($PSCmdlet.ParameterSetName) {
            "*Replace" {
                Write-Verbose -Message "Replacing current file permissions on $Path and forcing inheritance."
                $AccessRules = New-SQLInstanceInstallationDirectoryAccessRuleSet
                Set-FilePermissions -Path $Path -Rules $AccessRules -Replace:$Replace -ForceInheritance
                break
            }
            "*Inherit" {
                Write-Verbose -Message "Resetting inheritance on $Path."
                Reset-InheritedPermissions -Path $Path
                break
            }
            default {
                throw "Could not determine parameter set name."
            }
        }

        Write-Verbose -Message "Creating audit rules."
        $AuditRules = New-SQLInstanceInstallationDirectoryAuditRuleSet

        Write-Verbose -Message "Setting audit rules on $Path."
        Set-Auditing -Path $Path -AuditRules $AuditRules
    }
    
    End {
        Write-Verbose -Message "Completed setting installation directory permissions."
    }
}

Function Set-SQLInstanceAuditFilePermissions {
    <#
        .SYNOPSIS
            The audit information produced by SQL Server must be protected from unauthorized read access.
 
        .DESCRIPTION
            The Set-SQLInstanceAuditFilePermissions cmdlet sets the required security permissions for the SQL audit log files.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER AuditorAdministratorsName
            The name of the user or group responsible for the audit administrator role.
 
        .PARAMETER AuditorsName
            The name of the user or group responsible for auditing the database, primarily accessing audit log files.
 
        .EXAMPLE
            Set-SQLInstanceAuditFilePermissions -AuditAdministratorsName "UG-SQL-Audit-Admins" -AuditorsName "UG-SQL-Auditors"
             
            Configures the required permissions for the audit logs on the localhost for the default MSSQLSERVER instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
 
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-013600
                SQL4-00-013700
                SQL4-00-013800
            Rule ID
                SQL4-00-013600_rule
                SQL4-00-013700_rule
                SQL4-00-013800_rule
            Vuln ID
                SQL4-00-013600
                SQL4-00-013700
                SQL4-00-013800
            Severity
                CAT II
                CAT III
                CAT III
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        [string]$AuditorAdministratorsName,
        [Parameter(Position=1, Mandatory=$true)]
        [string]$AuditorsName,
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer
    )

    Begin
    {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
        
        Write-Verbose -Message "Setting Audit File Permissions."
        Write-Verbose -Message "STIG IDs: SQL4-00-013600, SQL4-00-013700, SQL4-00-013800"       
    }

    Process
    {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }
        
        <#if ($InstanceName -ieq "mssqlserver") {
            $InstanceServiceName = $InstanceName
            $SqlAgentService = "SQLSERVERAGENT"
        }
        else {
            $InstanceServiceName = "MSSQL`$$InstanceName"
            $SqlAgentService = "SQLAgent`$$InstanceName"
        }
 
        if ($ComputerName -iin $script:LocalNames) {
            $SqlServiceAccount = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$InstanceServiceName" -Name "ObjectName" | Select-Object -ExpandProperty ObjectName
            $SqlAgentServiceAccount = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$SqlAgentService" -Name "ObjectName" | Select-Object -ExpandProperty ObjectName
        }
        else {
            $Session = New-PSSession -ComputerName $ComputerName -Credential $Credential
            $SqlServiceAccount = Invoke-Command -Session $Session -ScriptBlock { Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($args[0])" -Name "ObjectName" | Select-Object -ExpandProperty ObjectName } -ArgumentList @($InstanceServiceName)
            $SqlAgentServiceAccount = Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($args[0])" -Name "ObjectName" | Select-Object -ExpandProperty ObjectName } -ArgumentList @($SqlAgentService)
            Remove-PSSession -Session $Session
        }
 
        $ServiceAccountSid = Get-AccountSid -UserName $SqlServiceAccount -ComputerName $ComputerName -Credential $Credential
        $SqlAgentAccountSid = Get-AccountSid -UserName $SqlAgentServiceAccount -ComputerName $ComputerName -Credential $Credential
        #>


        $Offset = 0
        Write-Verbose -Message "Getting service account SID."
        $ServiceAccount = $SqlConnection.Logins | Where-Object {$_.Name -eq $SqlConnection.ServiceAccount} | Select-Object -First 1         
        $ServiceAccountSid = New-Object System.Security.Principal.SecurityIdentifier($ServiceAccount.Sid, $Offset)

        Write-Verbose -Message "Getting SQL Agent service account SID."
        $SqlAgentAccount = $SqlConnection.Logins | Where-Object {$_.Name -eq $SqlConnection.JobServer.ServiceAccount} | Select-Object -First 1
        
        if ($SqlAgentAccount -eq $null) {
            Write-Verbose -Message "The SQL Agent service account returned null, getting the account through a query."
            $SqlAgentAccount = ($SqlConnection.Databases | Where-Object {$_.Name -eq "master"} | Select-Object -First 1).ExecuteWithResults("SELECT Sid FROM master.sys.dm_server_services svc JOIN master.sys.server_principals prin ON svc.service_account = prin.name WHERE svc.servicename LIKE 'SQL Server Agent%'").Tables[0] 
        }

        $SqlAgentAccountSid = New-Object System.Security.Principal.SecurityIdentifier([System.Byte[]]$SqlAgentAccount.Sid, $Offset)

        Write-Verbose -Message "Getting audit administrators SID."
        $AuditAdministratorsSid = Get-AccountSid -UserName $AuditorAdministratorsName

        Write-Verbose -Message "Getting auditors SID."
        $AuditorsSid = Get-AccountSid -UserName $AuditorsName

        Write-Verbose -Message "Building access rules."
        $AccessRules = New-SQLInstanceAuditLogAccessRuleSet -SqlServiceAccountSid $ServiceAccountSid -SqlAgentSid $SqlAgentAccountSid -AuditAdministratorSid $AuditAdministratorsSid -AuditorsSid $AuditorsSid

        Write-Verbose -Message "Getting error log path."
        $Directory = Get-SQLInstanceErrorLogPath -ComputerName $ComputerName -InstanceName $InstanceName

        Write-Verbose -Message "Transforming error log path."
        $ComputerName = $SqlConnection.Name
        if (!$Directory.StartsWith("\\") -and $ComputerName -inotin $script:LocalNames) {
            $Directory = $Directory.Replace(":\","`$\")
            $Directory = "\\$ComputerName\$Directory"
        }
        
        Write-Verbose -Message "Setting file permissions on $Directory."
        Set-FilePermissions -Path $Directory -Rules $AccessRules -Replace -ForceInheritance
    }

    End{
        Write-Verbose -Message "Completed setting audit file permissions."
    }
}

Function Set-SQLInstanceAuditors {
    <#
        .SYNOPSIS
            SQL Server must allow only the ISSM (or individuals or roles appointed by the ISSM) to select which auditable events are to be audited or traced.
 
        .DESCRIPTION
            The Set-SQLInstanceAuditors cmdlet creates logins for specified auditor Windows Groups, creates 2 roles, one for Server level auditing, and one for Database level auditing, grants them permissions
            and adds the new logins to the respective groups.
 
            Users or roles with existing ALTER ANY SERVER AUDIT or ALTER ANY DATABASE AUDIT permissions at the server or database level will have those privileges removed.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER ServerAuditorsRole
            The name of the role to be created for managing server level audits. Will be granted ALTER ANY SERVER AUDIT. Defaults to SERVER_AUDIT_MAINTAINERS.
 
        .PARAMETER DatabaseAuditorsRole
            The name of the role to be created for managing database level audits. Will be granted ALTER ANY DATABASE AUDIT on every existing database. Defaults to DATABASE_AUDIT_MAINTAINERS.
 
        .PARAMETER ServerAuditors
            The Windows user or Group that will have a login created and added to the new Server Auditors Role.
 
        .PARMETER DatabaseAuditors
            The Windows user or Group that will have a login created, then have a Database User created on every existing database, and added to the new Database Auditors Role.
 
        .EXAMPLE
            Set-SQLInstanceAuditors -ComputerName "SQL2014" -InstanceName "MyInstance" -ServerAuditors "Contoso\UG-SQL-Server-Auditors" -DatabaseAuditors "Contoso\UG-SQL-Database-Auditors"
             
            Creates logins for Contoso\UG-SQL-Server-Auditors and Contoso\UG-SQL-Database-Auditors. Creates the SERVER_AUDIT_MAINTAINERS and DATABASE_AUDIT_MAINTAINERS roles.
            Grants the required permissions to those roles. Adds a database user on each database for Contoso\UG-SQL-Database-Auditors. Remove existing privileges from other accounts and roles.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-011300
            Rule ID
                SQL4-00-011300_rule
            Vuln ID
                SQL4-00-011300
            Severity
                CAT II
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer,
        [Parameter()]
        [System.String]$ServerAuditorsRole = "SERVER_AUDIT_MAINTAINERS",
        [Parameter()]
        [System.String]$DatabaseAuditorsRole = "DATABASE_AUDIT_MAINTAINERS",
        [Parameter(Mandatory=$true)]
        [System.String]$ServerAuditors,
        [Parameter(Mandatory=$true)]
        [System.String]$DatabaseAuditors
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        if ([System.String]::IsNullOrEmpty($ServerAuditorsRole)) {
            $ServerAuditorsRole = "SERVER_AUDIT_MAINTAINERS"
        }

        if ([System.String]::IsNullOrEmpty($DatabaseAuditorsRole)) {
            $DatabaseAuditorsRole = "DATABASE_AUDIT_MAINTAINERS"
        }

        Write-Verbose -Message "Setting SQL Instance and Database Auditors."
        Write-Verbose -Message "STIG ID: SQL4-00-015350"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        [System.String[]]$Roles = $SqlConnection.Roles | Where-Object {$_.IsFixedRole -eq $false } | Select-Object -ExpandProperty Name
        [System.String[]]$Logins = $SqlConnection.Logins | Where-NotMatchIn -Matches $script:IgnoreNames -Property "Name" | Select-Object -ExpandProperty Name

        [System.String[]]$NewLogins = @($ServerAuditors, $DatabaseAuditors)
        [System.String[]]$NewRoles = @($ServerAuditorsRole, $DatabaseAuditorsRole)

        foreach ($Login in $NewLogins) {
            if ($SqlConnection.Settings.LoginMode -eq [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Mixed -or $SqlConnection.Settings.LoginMode -eq [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Normal) {
            
                if ($Login -like "*\*") {
                    Write-Verbose -Message "$Login matches domain account structure."
                    Add-SQLInstanceLogin -SqlServer $SqlConnection -Name $Login -LoginType WindowsUser
                }
                else {
                    Write-Verbose -Message "$Login will be created as a Sql user."
                    Add-SQLInstanceLogin -SqlServer $SqlConnection -Name $Login -LoginType SqlLogin
                }
            }
            else {
                Write-Verbose -Message "SQL Authentication is not enabled, $Login being created as a windows user."
                Add-SQLInstanceLogin -SqlServer $SqlConnection -Name $Login -LoginType WindowsUser
            }
        }

        [System.String]$Role = ""

        Add-SQLInstanceServerRole -SqlServer $SqlConnection -Name $ServerAuditorsRole

        $Principals = @()
        $Principals += $Logins
        $Principals += $Roles

        foreach ($Principal in $Principals) {
            Write-Verbose -Message "Reviewing ALTER ANY SERVER AUDIT permissions for $Principal."
            [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]]$Permissions = $SqlConnection.EnumServerPermissions($Principal)
    
            #PermissionType is a ServerPermissionSet object. The ServerPermissionSet object has properties that
            #correspond to the different permissions
            foreach ($ServerPermissionSet in $Permissions | Select-Object -ExpandProperty PermissionType) {
                if ($ServerPermissionSet.AlterAnyServerAudit -eq $true) {
                    Write-Verbose -Message "Revoking ALTER ANY SERVER AUDIT from $Principal."
                    $SqlConnection.Revoke([Microsoft.SqlServer.Management.Smo.ServerPermission]::AlterAnyServerAudit, $Principal)
                }
            }
        }

        Write-Verbose -Message "Granting ALTER ANY SERVER AUDIT to $ServerAuditorsRole."
        $SqlConnection.Grant([Microsoft.SqlServer.Management.Smo.ServerPermission]::AlterAnyServerAudit, $ServerAuditorsRole)

        Write-Verbose -Message "Adding $ServerAuditors to $ServerAuditorsRole."
        [Microsoft.SqlServer.Management.Smo.ServerRole]$SrvAuditorRole = $SqlConnection.Roles | Where-Object {$_.Name -eq $ServerAuditorsRole} | Select-Object -First 1
        $SrvAuditorRole.AddMember($ServerAuditors)

        foreach ($Database in $SqlConnection.Databases) {
            $DatabaseRoles = $Database.Roles | Where-Object { $_.IsFixedRole -eq $false } | Select-Object -ExpandProperty Name 
            $DatabaseUsers = $Database.Users | Where-NotMatchIn -Matches $script:IgnoreNames -Property "Name" | Select-Object -ExpandProperty Name

            [System.String[]]$Principals = @()
            $Principals += $DatabaseRoles
            $Principals += $DatabaseUsers

            foreach ($Principal in ($Principals | Where-Object {$_ -inotin @($ServerAuditorsRole, $DatabaseAuditorsRole)} )) {

                [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]]$DatabasePermissions = $Database.EnumDatabasePermissions($Principal) 

                foreach ($DatabasePermissionSet in $DatabasePermissions | Select-Object -ExpandProperty PermissionType) {
                    if ($DatabasePermissionSet.AlterAnyDatabaseAudit -eq $true) {
                        Write-Verbose -Message "Revoking ALTER ANY DATABASE AUDIT from $Principal."
                        $Database.Revoke([Microsoft.SqlServer.Management.Smo.DatabasePermission]::AlterAnyDatabaseAudit, $Principal)
                    }
                }
            }

            if ($DatabaseUsers -inotcontains $DatabaseAuditors) {
                Write-Verbose -Message "Creating database user for $DatabaseAuditors."
                [Microsoft.SqlServer.Management.Smo.User]$NewUser = New-Object Microsoft.SqlServer.Management.Smo.User($Database, $DatabaseAuditors)
                
                try {
                    $NewUser.Create()
                }
                catch [Exception] {
                    if ($_.Exception.InnerException -ne $null) {
                        $Exception = $_.Exception
                        while ($Exception.InnerException -ne $null) {                      
                            $Exception = $Exception.InnerException
                        }

                        throw $Exception
                    }
                    else {
                        throw $_.Exception
                    }
                }
            }
            else {
                Write-Verbose -Message "Database $($Database.Name) already contains a user for $DatabaseAuditors."
            }
            
            if ($DatabaseRoles -inotcontains $DatabaseAuditorsRole) {
                Write-Verbose -Message "Creating role $DatabaseAuditorsRole in database $($Database.Name)."
                [Microsoft.SqlServer.Management.Smo.DatabaseRole]$NewRole = New-Object Microsoft.SqlServer.Management.Smo.DatabaseRole($Database, $DatabaseAuditorsRole)
                
                try {
                    $NewRole.Create()
                }
                catch [Exception] {
                    if ($_.Exception.InnerException -ne $null) {
                        $Exception = $_.Exception
                        while ($Exception.InnerException -ne $null) {                      
                            $Exception = $Exception.InnerException
                        }

                        throw $Exception
                    }
                    else {
                        throw $_.Exception
                    }
                }
            }
            else {
                Write-Verbose -Message "Database $($Database.Name) already contains the role $DatabaseAuditorsRole."
            }

            Write-Verbose -Message "Granting ALTER ANY DATABASE AUDIT to $DatabaseAuditorsRole on $($Database.Name)."
            $Database.Grant([Microsoft.SqlServer.Management.Smo.DatabasePermission]::AlterAnyDatabaseAudit, $DatabaseAuditorsRole)

            Write-Verbose -Message "Adding $DatabaseAuditors to $DatabaseAuditorsRole in database $($Database.Name)."
            [Microsoft.SqlServer.Management.Smo.DatabaseRole]$DdAuditorsRole = $Database.Roles | Where-Object {$_.Name -eq $DatabaseAuditorsRole} | Select-Object -First 1
            $DdAuditorsRole.AddMember($DatabaseAuditors)
        }
    }

    End {
    }
}

Function Set-SQLInstanceAuditing {
    <#
        .SYNOPSIS
            SQL Server must generate trace or audit records on numerous actions.
 
        .DESCRIPTION
            The Set-SQLInstanceAuditing cmdlet creates an audit and an audit specification to audit all required events. It can optionally be set to include database level auditing, in which case it will also
            audit all SELECT, INSERT, UPDATE, DROP, and EXECUTE commands on every database. Use caution with this option as it will fill up logs quickly and is not required for all data per the STIG.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER AuditName
            The name of the audit to create. This defaults to STIG_Audit
 
        .PARAMETER AuditSpecificationName
            The name of the audit specification to create. This defaults to STIG_Audit_Specification.
 
        .PARAMETER AuditDestination
            The audit events can either be written to the Application Log, Security Log, or a Log File. Specify ApplicationLog, SecurityLog, or File. If the File option is selected, the FilePath parameter
            can also be used. The ApplicationLog is the default destination.
 
            Special considerations should be used when writing to the Security Log, see https://msdn.microsoft.com/en-us/library/cc645889.aspx for more details.
 
        .PARAMETER FilePath
            If the audit events are written to a file, you can specify the file they are written to. This defaults to "$env:SYSTEMROOT\Program Files\Microsoft SQL Server\Logs\AuditLog.trc".
 
            If ApplicationLog or SecurityLog are selected, this parameter is ignored.
 
        .PARMETER IncludeDatabaseLevelAudting
            This parameter specifies that all INSERT, UPDATE, SELECT, DELETE, and EXECUTE actions on every database except the master are audited. Use with caution since this will fill up the logs quickly.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Set-SQLInstanceAuditing -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Configures the auditing on the SQL Instance and writes the audits to the Application Log.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or Microsoft.SqlServer.Management.Smo.Audit
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-030400
                SQL4-00-011900
                SQL4-00-012000
                SQL4-00-012100
                SQL4-00-012200
                SQL4-00-012300
                SQL4-00-012400
                SQL4-00-030600
                SQL4-00-034000
                SQL4-00-035600
                SQL4-00-037500
                SQL4-00-037600
                SQL4-00-037700
                SQL4-00-037800
                SQL4-00-037900
                SQL4-00-038000
            Rule ID
                SQL4-00-030400_rule
                SQL4-00-011900_rule
                SQL4-00-012000_rule
                SQL4-00-012100_rule
                SQL4-00-012200_rule
                SQL4-00-012300_rule
                SQL4-00-012400_rule
                SQL4-00-030600_rule
                SQL4-00-034000_rule
                SQL4-00-035600_rule
                SQL4-00-037500_rule
                SQL4-00-037600_rule
                SQL4-00-037700_rule
                SQL4-00-037800_rule
                SQL4-00-037900_rule
                SQL4-00-038000_rule
            Vuln ID
                SQL4-00-030400
                SQL4-00-011900
                SQL4-00-012000
                SQL4-00-012100
                SQL4-00-012200
                SQL4-00-012300
                SQL4-00-012400
                SQL4-00-030600
                SQL4-00-034000
                SQL4-00-035600
                SQL4-00-037500
                SQL4-00-037600
                SQL4-00-037700
                SQL4-00-037800
                SQL4-00-037900
                SQL4-00-038000
            Severity
                CAT II
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer = $null,
        [Parameter()]
        [System.String]$AuditName = "STIG_Audit",
        [Parameter()]
        [System.String]$AuditSpecificationName = "STIG_Audit_Specification",
        [Parameter()]
        [ValidateSet("ApplicationLog", "SecurityLog", "File")]
        [System.String]$AuditDestination = "ApplicationLog",
        [Parameter()]
        [System.String]$FilePath = "$env:SYSTEMROOT\Program Files\Microsoft SQL Server\Logs\AuditLog.trc",
        [Parameter()]
        [switch]$IncludeDatabaseLevelAudting,
        [Parameter()]
        [switch]$PassThru
    )

    Begin{
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Setting SQL Instance Auditing."
        Write-Verbose -Message "STIG ID: SQL4-00-030400, SQL4-00-011900, SQL4-00-012000, SQL4-00-012100, SQL4-00-012200, SQL4-00-012300, SQL4-00-012400, SQL4-00-030600, SQL4-00-034000, SQL4-00-035600, SQL4-00-037500, SQL4-00-037600, SQL4-00-037700, SQL4-00-037800, SQL4-00-037900, SQL4-00-038000"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if (($SqlConnection.Audits | Where-Object {$_.Name -ieq $AuditName}) -eq $null) {
            Write-Verbose -Message "Creating audit $AuditName."

            [Microsoft.SqlServer.Management.Smo.Audit]$ServerAudit = New-Object Microsoft.SqlServer.Management.Smo.Audit($SqlConnection, $AuditName)
            $ServerAudit.OnFailure = [Microsoft.SqlServer.Management.Smo.OnFailureAction]::Continue
            switch ($AuditDestination) {
                "ApplicationLog" {
                    $ServerAudit.DestinationType = [Microsoft.SqlServer.Management.Smo.AuditDestinationType]::ApplicationLog
                    break
                }
                "SecurityLog" {
                    $ServerAudit.DestinationType = [Microsoft.SqlServer.Management.Smo.AuditDestinationType]::SecurityLog
                    break
                }
                "File" {
                    $ServerAudit.DestinationType = [Microsoft.SqlServer.Management.Smo.AuditDestinationType]::File
                    $ServerAudit.FilePath = $FilePath
                    break
                }
            }
            
            $ServerAudit.Enable()
            
            try {
                $ServerAudit.Create()
            }
            catch [Exception] {
                if ($_.Exception.InnerException -ne $null) {
                    $Exception = $_.Exception
                    while ($Exception.InnerException -ne $null) {                      
                        $Exception = $Exception.InnerException
                    }

                    throw $Exception
                }
                else {
                    throw $_.Exception
                }
            }
        }
        else {
            Write-Verbose -Message "Server already contains an audit named $AuditName."
            [Microsoft.SqlServer.Management.Smo.Audit]$ServerAudit = $SqlConnection.Audits | Where-Object {$_.Name -ieq $AuditName} | Select-Object -First 1
            $ServerAudit.Enable()
        }

        if (($SqlConnection.ServerAuditSpecifications | Where-Object {$_.Name -ieq $AuditSpecificationName}) -eq $null) {
            Write-Verbose -Message "Creating audit specification $AuditSpecificationName."
            [Microsoft.SqlServer.Management.Smo.AuditSpecification]$ServerAuditSpecification = [Microsoft.SqlServer.Management.Smo.AuditSpecification]$ServerAuditSpecification = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AuditSpecification($SqlConnection, $AuditSpecificationName)
            $ServerAuditSpecification.AuditName = $AuditName
            
            try {
                $ServerAuditSpecification.Create()  
            }
            catch [Exception] {
                if ($_.Exception.InnerException -ne $null) {
                    $Exception = $_.Exception
                    while ($Exception.InnerException -ne $null) {                      
                        $Exception = $Exception.InnerException
                    }

                    throw $Exception
                }
                else {
                    throw $_.Exception
                }
            }
        }
        else {
            Write-Verbose -Message "Server already contains an audit specification named $AuditSpecificationName. Disabling the specification to modify it."
            [Microsoft.SqlServer.Management.Smo.AuditSpecification]$ServerAuditSpecification = $SqlConnection.ServerAuditSpecifications |  Where-Object {$_.Name -ieq $AuditSpecificationName} | Select-Object -First 1
            $ServerAuditSpecification.Disable()
        }

        foreach ($Action in ([Enum]::GetNames([Microsoft.SqlServer.Management.Smo.AuditActionType]) | Where-Object {$_ -like "*Group"})) {
            [Microsoft.SqlServer.Management.Smo.AuditSpecificationDetail]$SpecificationDetail = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AuditSpecificationDetail($Action)
            $ServerAuditSpecification.AddAuditSpecificationDetail($SpecificationDetail)
        }

        if ($IncludeDatabaseLevelAudting) {
            foreach ($Database in ($SqlConnection.Databases | Where-Object {$_.Name -ine "master" })) {
                Set-SQLDatabaseAuditing -Database $Database -SqlServer $SqlConnection -AuditName $AuditName -DatabaseAuditSpecificationName $AuditSpecificationName -Audits $script:DatabaseAudits
            }
        }
        
        $ServerAuditSpecification.Enable()  
    }

    End {
        if ($PassThru) {
            Write-Output $ServerAudit
        }
    }
}

Function Set-SQLDatabaseAuditing {
    <#
        .SYNOPSIS
            SQL Server must generate trace or audit records on numerous actions.
 
        .DESCRIPTION
            The Set-SQLDatabaseAuditing cmdlet creates a database audit specification to audit all selected events. This defaults to audit all SELECT, INSERT, UPDATE, DROP, and EXECUTE commands
     
            Use caution with this option as it will fill up logs quickly and is not required for all data per the STIG.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Database
            An existing database of the type Microsoft.SqlServer.Management.Smo.Database to create the auditing on.
 
        .PARAMETER AuditName
            The name of the audit to use, this should be an existing audit that the new Audit Specification is added to.
 
        .PARAMETER DatabaseAuditSpecificationName
            The name of the database audit specification to create.
 
        .PARAMETER Audits
            The specific audits to include, choose any combination of SELECT, INSERT, UPDATE, DROP, and EXECUTE. This defaults to all.
 
        .PARAMETER Principal
            The principal to audit taking these actions. This parameter defaults to public, which audits actions from all users.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Set-SQLInstanceAuditing -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Configures the auditing on the SQL Instance and writes the audits to the Application Log.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or Microsoft.SqlServer.Management.Smo.DatabaseAuditSpecification
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer,
        [Parameter(Mandatory=$true)]
        $Database,
        [Parameter(Mandatory=$true)]
        [System.String]$AuditName,
        [Parameter(Mandatory=$true)]
        [System.String]$DatabaseAuditSpecificationName,
        [Parameter()]
        [ValidateSet("SELECT", "INSERT", "UPDATE", "DELETE", "EXECUTE", IgnoreCase = $true)]
        [ValidateScript({$_.Count -ge 1})]
        [System.String[]]$Audits = $script:DatabaseAudits,
        [Parameter()]
        [System.String]$Principal = "public",
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        if ($Database.GetType() -ne [Microsoft.SqlServer.Management.Smo.Database] -and $Database.GetType() -ne [System.String]) {
            throw "Database parameter must be of type Microsoft.SqlServer.Management.Smo.Database or System.String."
        }

        Write-Verbose -Message "Setting Database Level Auditing."
        Write-Verbose -Message "STIG ID: "
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if ($Database.GetType().Equals([System.String])) {
            [Microsoft.SqlServer.Management.Smo.Database]$Database = $SqlConnection.Databases | Where-Object {$_.Name -eq $Database.Name } | Select-Object -First 1
        }

        if ($Database -ne $null) {

            if (($Database.DatabaseAuditSpecifications | Where-Object { $_.Name -ieq $DatabaseAuditSpecificationName }) -eq $null) {
                Write-Verbose -Message "Creating database audit specification $DatabaseAuditSpecificationName on $($Database.Name)."
                [Microsoft.SqlServer.Management.Smo.DatabaseAuditSpecification]$DatabaseAuditSpecification = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseAuditSpecification($Database, $DatabaseAuditSpecificationName)
                $DatabaseAuditSpecification.AuditName = $AuditName
                
                try {
                    $DatabaseAuditSpecification.Create()
                    Write-Verbose -Message "Specification enabled $($DatabaseAuditSpecification.Enabled)"
                }
                catch [Exception] {
                    if ($_.Exception.InnerException -ne $null) {
                        $Exception = $_.Exception
                        while ($Exception.InnerException -ne $null) {                      
                            $Exception = $Exception.InnerException
                        }

                        throw $Exception
                    }
                    else {
                        throw $_.Exception
                    }
                }
            }
            else {
                Write-Verbose -Message "Database $($Database.Name) already contains a database audit specification named $DatabaseAuditSpecificationName. Disabling the specification to modify it."
                [Microsoft.SqlServer.Management.Smo.DatabaseAuditSpecification]$DatabaseAuditSpecification = $Database.DatabaseAuditSpecifications | Where-Object { $_.Name -ieq $DatabaseAuditSpecificationName} | Select-Object -First 1
                $DatabaseAuditSpecification.Disable()
            }
         
            foreach($Action in ([Enum]::GetNames([Microsoft.SqlServer.Management.Smo.AuditActionType]) | Where-Object {$_ -iin $Audits } )) {
                $ObjectClass = "Database"
                $ObjectSchema = [System.String]::Empty
                [Microsoft.SqlServer.Management.Smo.AuditSpecificationDetail]$SpecificationDetail = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AuditSpecificationDetail($Action, $ObjectClass, $ObjectSchema, $Database.Name, $Principal)
                $DatabaseAuditSpecification.AddAuditSpecificationDetail($SpecificationDetail)
            }

            $DatabaseAuditSpecification.Enable()
        }
        else {
            throw "Could not find a database named $Database."
        }
    }

    End {
        if ($PassThru) {
            Write-Output $DatabaseAuditSpecification
        }
    }
}

Function Set-SQLInstanceManagementRoles {
    <#
        .SYNOPSIS
            SQL Server must be configured to separate user functionality (including user interface services) from database management functionality.
 
            SQL Server must prevent non-privileged users from executing privileged functionality, to include disabling, circumventing, or altering implemented security safeguards/countermeasures.
 
            SQL Server and Windows must enforce access restrictions associated with changes to the configuration of the SQL Server instance or database(s).
 
        .DESCRIPTION
            The Set-SQLInstanceManagementRoles cmdlet creates new roles with the same permissions as the key built in roles: sysadmin, serveradmin, and dbcreator. It also creates a role granted CONNECT SQL.
 
            Then, identified Windows Users, Groups, or SQL Logins are created if they do not already exist and added to these roles.
 
            Any other logins or roles explicitly granted these permissions will have them revoked and will be added to the new roles. Any members of the built in roles will be removed and added to the new
            roles, except for system accounts in the sysadmin role like NT SERVICE, NT AUTHORITY, sys, INFORMATION_SCHEMA or ##*## accounts.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER ConnectSqlRoleName
            The name of the role being granted CONNECT SQL to create. This defaults to CONNECT_SQL_ROLE.
 
        .PARAMETER SysAdminRoleName
            The name of the role being granted CONTROL SERVER to create. This defaults to SYSADMIN_ROLE.
 
        .PARAMETER ServerAdminRoleName
            The name of the role being granted the server admin permissions to create. This defaults to SERVERADMIN_ROLE.
 
        .PARAMETER DBCreatorRoleName
            The name of the role being granted CREATE ANY DATABASE to create. This defaults to DBCREATOR_ROLE.
 
        .PARAMETER SysAdminRoleMembers
            The Server Logins that should belong to the new Server Role. Logins will be created if they don't already exist.
 
        .PARAMETER ServerAdminRoleMembers
            The Server Logins that should belong to the new Server Role. Logins will be created if they don't already exist.
 
        .PARAMETER DBCreatorRoleMembers
            The Server Logins that should belong to the new Server Role. Logins will be created if they don't already exist.
 
        .PARAMETER ConnectSqlRoleMembers
            The Server Logins that should belong to the new Server Role. Logins will be created if they don't already exist.
 
        .PARAMETER DefaultSysAdmins
            The Server Logins in this list will not be removed from the sysadmin role during the execution of this cmdlet to preserve the capability to perform functions that explicitly check for membership in sysadmin.
 
        .EXAMPLE
            Set-SQLInstanceManagementRoles -ComputerName "SQL2014" `
                                            -InstanceName "MyInstance" `
                                            -SysAdminRoleMembers @("Contoso\UG-SQL-DEV_001-SysAdmins") `
                                            -ServerAdminRoleMembers @("Contoso\UG-SQL-DEV_001-ServerAdmins") `
                                            -DBCreatorRoleMembers @("Contoso\UG-SQL-DEV_001-DBCreators")
                                             
            Creates the 4 roles with their default names and creates 3 new logins and adds those logins to the corresponding role. Existing privileges are transferred to the new roles.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-020500
                SQL4-00-032500
                SQL4-00-033900
            Rule ID
                SQL4-00-020500_rule
                SQL4-00-032500_rule
                SQL4-00-033900_rule
            Vuln ID
                SQL4-00-020500
                SQL4-00-032500
                SQL4-00-033900
            Severity
                CAT II
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer,
        [Parameter()]
        [System.String]$ConnectSqlRoleName = "CONNECT_SQL_ROLE",
        [Parameter()]
        [System.String]$SysAdminRoleName = "SYSADMIN_ROLE",
        [Parameter()]
        [System.String]$ServerAdminRoleName = "SERVERADMIN_ROLE",
        [Parameter()]
        [System.String]$DBCreatorRoleName = "DBCREATOR_ROLE",
        [Parameter()]
        [System.String[]]$SysAdminRoleMembers,
        [Parameter()]
        [System.String[]]$ServerAdminRoleMembers,
        [Parameter()]
        [System.String[]]$DBCreatorRoleMembers,
        [Parameter()]
        [System.String[]]$ConnectSqlRoleMembers,
        [Parameter()]
        [System.String[]]$DefaultSysAdmins
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Creating SQL Instance Management Roles."
        Write-Verbose -Message "STIG ID: SQL4-00-020500, SQL4-00-032500, SQL4-00-033900"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        #Create logins for specified new role members
        $NewLogins = @()
        $NewLogins += $SysAdminRoleMembers
        $NewLogins += $ServerAdminRoleMembers
        $NewLogins += $DBCreatorRoleMembers
        $NewLogins += $ConnectSqlRoleMembers

        foreach ($Login in ($NewLogins | Where-Object {![System.String]::IsNullOrEmpty($_) })) {
            if ($SqlConnection.Settings.LoginMode -eq [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Mixed -or $SqlConnection.Settings.LoginMode -eq [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Normal) {
            
                if ($Login -like "*\*") {
                    Write-Verbose -Message "$Login matches domain account structure."
                    Add-SQLInstanceLogin -SqlServer $SqlConnection -Name $Login -LoginType WindowsUser
                }
                else {
                    Write-Verbose -Message "$Login will be created as a Sql user."
                    Add-SQLInstanceLogin -SqlServer $SqlConnection -Name $Login -LoginType SqlLogin
                }
            }
            else {
                Write-Verbose -Message "SQL Authentication is not enabled, $Login being created as a windows user."
                Add-SQLInstanceLogin -SqlServer $SqlConnection -Name $Login -LoginType WindowsUser
            }
        }

        #Create new roles
        [Microsoft.SqlServer.Management.Smo.ServerRole]$SysAdminRole = Add-SQLInstanceServerRole -SqlServer $SqlConnection -Name $SysAdminRoleName -PassThru
        [Microsoft.SqlServer.Management.Smo.ServerRole]$ServerAdminRole = Add-SQLInstanceServerRole -SqlServer $SqlConnection -Name $ServerAdminRoleName -PassThru
        [Microsoft.SqlServer.Management.Smo.ServerRole]$DBCreatorRole = Add-SQLInstanceServerRole -SqlServer $SqlConnection -Name $DBCreatorRoleName -PassThru
        [Microsoft.SqlServer.Management.Smo.ServerRole]$ConnectSqlRole = Add-SQLInstanceServerRole -SqlServer $SqlConnection -Name $ConnectSqlRoleName -PassThru

        [Microsoft.SqlServer.Management.Smo.ServerPermissionSet]$ServerAdminPermissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerPermissionSet
        $ServerAdminPermissionSet.AlterAnyEndpoint = $true
        $ServerAdminPermissionSet.AlterResources = $true
        $ServerAdminPermissionSet.AlterServerState = $true
        $ServerAdminPermissionSet.AlterSettings = $true
        $ServerAdminPermissionSet.Shutdown = $true
        $ServerAdminPermissionSet.ViewServerState = $true

        #Assign permissions to roles
        $SqlConnection.Grant([Microsoft.SqlServer.Management.Smo.ServerPermission]::ControlServer, $SysAdminRole.Name, $true)
        $SqlConnection.Grant($ServerAdminPermissionSet, $ServerAdminRole.Name)
        $SqlConnection.Grant([Microsoft.SqlServer.Management.Smo.ServerPermission]::CreateAnyDatabase, $DBCreatorRole.Name)
        $SqlConnection.Grant([Microsoft.SqlServer.Management.Smo.ServerPermission]::ConnectSql, $ConnectSqlRole.Name)
    
        #Assign new logins as members to the new roles
        if ($SysAdminRoleMembers.Count -gt 0) {
            Write-Verbose -Message "Adding $($SysAdminRoleMembers -join `",`") to $SysAdminRoleName."
            $SysAdminRoleMembers | ForEach-Object { $SysAdminRole.AddMember($_) }
        }
        else {
            Write-Verbose -Message "No members in SysAdminRoleMembers."
        }

        if ($ServerAdminRoleMembers.Count -gt 0) {
            Write-Verbose -Message "Adding $($ServerAdminRoleMembers -join `",`") to $ServerAdminRoleName."
            $ServerAdminRoleMembers | ForEach-Object { $ServerAdminRole.AddMember($_) }
        }
        else {
            Write-Verbose -Message "No members in ServerAdminRoleMembers."
        }

        if ($DBCreatorRoleMembers.Count -gt 0) {
            Write-Verbose -Message "Adding $($DBCreatorRoleMembers -join `",`") to $DBCreatorRoleName."
            $DBCreatorRoleMembers | ForEach-Object { $DBCreatorRole.AddMember($_) }
        }
        else {
            Write-Verbose -Message "No members in DBCreatorRoleMembers."
        }

        if ($ConnectSqlRoleMembers.Count -gt 0) {
            Write-Verbose -Message "Adding $($ConnectSqlRoleMembers -join `",`") to $ConnectSqlRoleName."
            $ConnectSqlRoleMembers | ForEach-Object { $ConnectSqlRole.AddMember($_) }
        }
        else {
            Write-Verbose -Message "No members in ConnectSqlRoleMembers."
        }

        Write-Verbose -Message "Reviewing existing principals and altering permissions."

        [System.String[]]$Logins = $SqlConnection.Logins | Where-Object {$_.Sid -ne 0x01} | Where-NotMatchIn -Matches $script:IgnoreNames -Property "Name" | Select-Object -ExpandProperty Name
        [System.String[]]$Roles = $SqlConnection.Roles  | Where-Object {$_.IsFixedRole -eq $false -and $_.Name -inotin @($SysAdminRoleName, $ServerAdminRoleName, $ConnectSqlRoleName, $DBCreatorRoleName) } | Select-Object -ExpandProperty Name

        [System.String[]]$Principals = @()
        $Principals += $Logins
        $Principals += $Roles

        Write-Verbose -Message "Reviewing principals $($Principals -join `",`")"

        #Check explicit permissions on each principal
        foreach ($Principal in $Principals) {
            [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]]$Permissions = $SqlConnection.EnumServerPermissions($Principal)
            Write-Verbose -Message "Reviewing explicit permissions for $Principal."

            #PermissionType is a ServerPermissionSet object. The ServerPermissionSet object has properties that
            #correspond to the different permissions
            foreach ($ServerPermissionSet in $Permissions | Select-Object -ExpandProperty PermissionType) {
                
                if ($ServerPermissionSet.ControlServer -eq $true) {
                    Write-Verbose -Message "Adding $Principal to $SysAdminRoleName."
                    $SysAdminRole.AddMember($Principal)

                    Write-Verbose -Message "Revoking CONTROL SERVER from $Principal."
                    $SqlConnection.Revoke([Microsoft.SqlServer.Management.Smo.ServerPermission]::ControlServer, $Principal)
                }

                if ($ServerPermissionSet.AlterAnyEndpoint -eq $true -and $ServerPermissionSet.AlterResources -eq $true -and $ServerPermissionSet.AlterServerState -eq $true -and
                    $ServerPermissionSet.AlterSettings -eq $true -and $ServerPermissionSet.Shutdown -eq $true -and $ServerPermissionSet.ViewServerState -eq $true) {
                    Write-Verbose -Message "Adding $Principal to $ServerAdminRoleName."
                    $ServerAdminRole.AddMember($Principal)
                        
                    Write-Verbose -Message "Revoking Server Admin permissions from $Principal."
                    $SqlConnection.Revoke($ServerAdminPermissionSet, $Principal)
                }

                if ($ServerPermissionSet.CreateAnyDatabase -eq $true) {
                    Write-Verbose -Message "Adding $Principal to $DBCreatorRoleName."
                    $DBCreatorRole.AddMember($Principal)

                    Write-Verbose -Message "Revoking CREATE ANY DATABASE from $Principal"
                    $SqlConnection.Revoke([Microsoft.SqlServer.Management.Smo.ServerPermission]::CreateAnyDatabase, $Principal)
                }

                if ($ServerPermissionSet.ConnectSql -eq $true) {
                    Write-Verbose -Message "Adding $Principal to $ConnectSqlRoleName."
                    $ConnectSqlRole.AddMember($Principal)

                    Write-Verbose -Message "Revoking CONNECT SQL from $Principal"
                    $SqlConnection.Revoke([Microsoft.SqlServer.Management.Smo.ServerPermission]::ConnectSql, $Principal)
                }        
            }
        }

        #Review fixed roles

        Write-Verbose -Message "Reviewing fixed server roles and moving members."

        #Sysadmin SID = 0x03
        [Microsoft.SqlServer.Management.Smo.ServerRole]$SysAdminFixedRole = $SqlConnection.Roles | Where-Object {$_.IsFixedRole -eq $true -and $_.ID -eq 0x03} | Select-Object -First 1

        #Don't modify sa or builtin NT SERVICE logins like SQLSERVERAGENT or MSSQLSERVER
        foreach ($Member in ($SysAdminFixedRole.EnumMemberNames() | Where-NotMatchIn -Matches $script:IgnoreNames | Where-Object {$_ -inotin $DefaultSysAdmins})) {            
            if (($SqlConnection.Logins | Where-Object {$_.Name -eq $Member -and $_.Sid -ne 0x01} ) -ne $null) {
                Write-Verbose -Message "Adding $Member to $SysAdminRoleName."
                $SysAdminRole.AddMember($Member)

                Write-Verbose -Message "Removing $Member from $($SysAdminFixedRole.Name)."
                $SysAdminFixedRole.DropMember($Member)
            }
        }

        #ServerAdmin SID = 0x05
        [Microsoft.SqlServer.Management.Smo.ServerRole]$ServerAdminFixedRole = $SqlConnection.Roles | Where-Object {$_.IsFixedRole -eq $true -and $_.ID -eq 0x05} | Select-Object -First 1

        foreach ($Member in $ServerAdminFixedRole.EnumMemberNames()) {
            Write-Verbose -Message "Adding $Member to $ServerAdminRoleName."
            $ServerAdminRole.AddMember($Member)

            Write-Verbose -Message "Removing $Member from $($ServerAdminFixedRole.Name)."
            $ServerAdminFixedRole.DropMember($Member)
        }

        #DBCreator SID = 0x09
        [Microsoft.SqlServer.Management.Smo.ServerRole]$DBCreatorFixedRole =  $SqlConnection.Roles | Where-Object {$_.IsFixedRole -eq $true -and $_.ID -eq 0x09} | Select-Object -First 1

        foreach ($Member in $DBCreatorFixedRole.EnumMemberNames()) {
            Write-Verbose -Message "Adding $Member to $DBCreatorRoleName."
            $DBCreatorRole.AddMember($Member)

            Write-Verbose -Message "Removing $Member from $($DBCreatorFixedRole.Name)."
            $DBCreatorFixedRole.DropMember($Member)
        }
    }

    End {

    }
}

Function Rename-SQLInstanceAccount {
    <#
        .SYNOPSIS
            Renames a SQL Login.
 
        .DESCRIPTION
            The Rename-SQLInstanceAccount cmdlet renames a provided login.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
            The current name of the account to rename.
 
        .PARAMETER Sid
            The SID of the login principal to rename.
 
        .PARAMETER NewName
            The new name of the login.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Rename-SQLInstanceAccount -ComputerName "SQL2014" -Instance "MyInstance" -Name "sa" -NewName "xsa"
             
            Renames the sa account to xsa.
 
        .EXAMPLE
            Rename-SQLInstanceAccount -ComputerName "SQL2014" -Instance "MyInstance" -Sid 0x01 -NewName "xsa"
             
            Renames the account with the SID 0x01 (the sa account) to xsa.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or Microsoft.SqlServer.Management.Smo.Login
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-010200
            Rule ID
                SQL4-00-010200_rule
            Vuln ID
                SQL4-00-010200
            Severity
                CAT III
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithSid")]
        [System.String]$ComputerName = "localhost",
        
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithSid")]
        [System.String]$InstanceName = "MSSQLSERVER",

        [Parameter(ParameterSetName="ExistingConnectionWithName",Mandatory=$true,Position=0)]
        [Parameter(ParameterSetName="ExistingConnectionWithSid",Mandatory=$true,Position=0)]
        $SqlServer,

        [Parameter(Position=1,Mandatory=$true,ParameterSetName="ExistingConnectionWithName")]
        [Parameter(Position=0,Mandatory=$true,ParameterSetName="NewConnectionWithName")]
        [System.String]$Name,

        [Parameter(Position=1,Mandatory=$true,ParameterSetName="ExistingConnectionWithSid")]
        [Parameter(Position=0,Mandatory=$true,ParameterSetName="NewConnectionWithSid")]
        $Sid,

        [Parameter(Position=1,Mandatory=$true,ParameterSetName="NewConnectionWithName")]
        [Parameter(Position=1,Mandatory=$true,ParameterSetName="NewConnectionWithSid")]
        [Parameter(Position=2,Mandatory=$true,ParameterSetName="ExistingConnectionWithSid")]
        [Parameter(Position=2,Mandatory=$true,ParameterSetName="ExistingConnectionWithName")]
        [System.String]$NewName,

        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Renaming SQL Login."
        Write-Verbose -Message "STIG ID: SQL4-00-010200"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -ilike "ExistingConnection*") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        switch -wildcard ($PSCmdlet.ParameterSetName) {
            "*Name" {
                Write-Verbose -Message "Matching login by name, $Name."

                [Microsoft.SqlServer.Management.Smo.Login]$Login = $SqlConnection.Logins | Where-Object {$_.Name -ieq $Name } | Select-Object -First 1
                Write-Verbose -Message "Found login is null: $($Login -eq $null)"
                break
            }
            "*Sid" {
                Write-Verbose -Message "Matching login by sid, $Sid."

                if ($Sid.GetType() -eq [System.Byte[]]) {
                    [System.Byte[]]$Value = $Sid
                }
                if ($Sid.GetType() -eq [System.String]) {
                    [System.Security.Principal.SecurityIdentifier]$Temp = New-Object System.Security.Principal.SecurityIdentifier($Sid)                   
                    [System.Byte[]]$Value = New-Object System.Byte[]($Temp.BinaryLength)
                    $Temp.GetBinaryForm($Value, 0)
                }
                if ($Sid.GetType() -eq [System.Int32]) {
                    
                    [System.Byte[]]$Value = [System.BitConverter]::GetBytes($Sid)
                }

                [Microsoft.SqlServer.Management.Smo.Login]$Login = $SqlConnection.Logins | Where-Object {
                    
                    if ($_.Sid.Length -lt 4) {
                        [System.Byte[]]$TempArr = New-Object System.Byte[](4)
                        $_.Sid.CopyTo($TempArr, 0)
                    }
                    else {
                        [System.Byte[]]$TempArr = New-Object System.Byte[]($_.Sid.Length)
                        $TempArr = $_.Sid
                    }

                    [System.BitConverter]::ToInt32($Value, 0) -eq [BitConverter]::ToInt32($TempArr, 0)
                    
                } | Select-Object -First 1

                break
            }
        }

        if ($Login -ne $null) {
            $Login.Rename($NewName)
        }
        else {
            Write-Warning "Could not find a login matching the provided information."
        }
    }

    End {
        if ($PassThru) {
            Write-Output $Login
        }
    }
}

Function Disable-SQLInstanceAccount {
    <#
        .SYNOPSIS
            Disables a SQL Login.
 
        .DESCRIPTION
            The Disable-SQLInstanceAccount cmdlet disables a provided login.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
            The current name of the account to disable.
 
        .PARAMETER Sid
            The SID of the login principal to disable.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Disable-SQLInstanceAccount -ComputerName "SQL2014" -InstanceName "MyInstance" -Name "sa"
             
            Disables the sa account
 
        .EXAMPLE
            Rename-SQLInstanceAccount -ComputerName "SQL2014" -InstanceName "MyInstance" -Sid 0x01
             
            Disables the account with the SID 0x01 (the sa account).
 
        .INPUTS
            None
 
        .OUTPUTS
            None or Microsoft.SqlServer.Management.Smo.Login
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-017100
            Rule ID
                SQL4-00-017100_rule
            Vuln ID
                SQL4-00-017100
            Severity
                CAT II
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithSid")]
        [System.String]$ComputerName = "localhost",
        
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithSid")]
        [System.String]$InstanceName = "MSSQLSERVER",

        [Parameter(ParameterSetName="ExistingConnectionWithName",Mandatory=$true,Position=0)]
        [Parameter(ParameterSetName="ExistingConnectionWithSid",Mandatory=$true,Position=0)]
        $SqlServer,

        [Parameter(Position=1,Mandatory=$true,ParameterSetName="ExistingConnectionWithName")]
        [Parameter(Position=0,Mandatory=$true,ParameterSetName="NewConnectionWithName")]
        [System.String]$Name,

        [Parameter(Position=1,Mandatory=$true,ParameterSetName="ExistingConnectionWithSid")]
        [Parameter(Position=0,Mandatory=$true,ParameterSetName="NewConnectionWithSid")]
        $Sid,

        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Disabling SQL Login."
        Write-Verbose -Message "STIG ID: SQL4-00-017100"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -ilike "ExistingConnection*") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        switch -wildcard ($PSCmdlet.ParameterSetName) {
            "*Name" {
                Write-Verbose -Message "Matching login by name, $Name."

                [Microsoft.SqlServer.Management.Smo.Login]$Login = $SqlConnection.Logins | Where-Object {$_.Name -ieq $Name } | Select-Object -First 1
                Write-Verbose -Message "Found login is null: $($Login -eq $null)"
                break
            }
            "*Sid" {
                Write-Verbose -Message "Matching login by sid, $Sid."

                if ($Sid.GetType() -eq [System.Byte[]]) {
                    [System.Byte[]]$Value = $Sid
                }
                if ($Sid.GetType() -eq [System.String]) {
                    [System.Security.Principal.SecurityIdentifier]$Temp = New-Object System.Security.Principal.SecurityIdentifier($Sid)                   
                    [System.Byte[]]$Value = New-Object System.Byte[]($Temp.BinaryLength)
                    $Temp.GetBinaryForm($Value, 0)
                }
                if ($Sid.GetType() -eq [System.Int32]) {
                    
                    [System.Byte[]]$Value = [System.BitConverter]::GetBytes($Sid)
                }

                [Microsoft.SqlServer.Management.Smo.Login]$Login = $SqlConnection.Logins | Where-Object {
                    
                    if ($_.Sid.Length -lt 4) {
                        [System.Byte[]]$TempArr = New-Object System.Byte[](4)
                        $_.Sid.CopyTo($TempArr, 0)
                    }
                    else {
                        [System.Byte[]]$TempArr = New-Object System.Byte[]($_.Sid.Length)
                        $TempArr = $_.Sid
                    }

                    [System.BitConverter]::ToInt32($Value, 0) -eq [BitConverter]::ToInt32($TempArr, 0)
                    
                } | Select-Object -First 1

                break
            }
        }

        if ($Login -ne $null) {
            $Login.Disable()
        }
        else {
            Write-Warning "Could not find a login matching the provided information."
        }
    }

    End {
        if ($PassThru) {
            Write-Output $Login
        }
    }
}

Function Set-SQLInstanceXPCmdShell{
    <#
        .SYNOPSIS
            Access to xp_cmdshell must be disabled, unless specifically required and approved.
 
        .DESCRIPTION
            The Set-SQLInstanceXPCmdShell cmdlet enables or disables xp_cmdshell.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Enabled
            Specifies whether xp_cmdshell should be enabled or disabled. Defaults to false.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Set-SQLInstanceXPCmdShell -ComputerName "SQL2014" -InstanceName "MyInstance" -Enabled $false
             
            Disables xp_cmdshell on the specified instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-017200
            Rule ID
                SQL4-00-017200_rule
            Vuln ID
                SQL4-00-017200
            Severity
                CAT II
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter()]
        [System.Boolean]$Enabled = $false,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Disabling XP_Cmdshell."
        Write-Verbose -Message "STIG ID: SQL4-00-017200"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        Write-Verbose -Message "Enabling Show Advanced Options."
        $Max = $SqlConnection.Configuration.ShowAdvancedOptions.Maximum
        $Min = $SqlConnection.Configuration.ShowAdvancedOptions.Minimum
        Write-Verbose -Message "ShowAdvancedOptions mininum: $Min maximum: $Max"
        $SqlConnection.Configuration.ShowAdvancedOptions.ConfigValue = $Max
        $SqlConnection.Configuration.Alter()
        Write-Verbose -Message "Setting xp_cmdshell enabled to $Enabled."
        $SqlConnection.Configuration.XPCmdShellEnabled.ConfigValue = $Enabled
        $SqlConnection.Configuration.Alter()
        Write-Verbose -Message "Disabling Show Advanced Options."
        $SqlConnection.Configuration.ShowAdvancedOptions.ConfigValue = $Min
        $SqlConnection.Configuration.Alter()
    }

    End {
        if ($PassThru) {
            Write-Output $SqlConnection.Configuration.XPCmdShellEnabled
        }
    }
}

Function Set-SQLInstanceLoginPasswordPolicies {
    <#
        .SYNOPSIS
            If SQL Server authentication, using passwords, is employed, SQL Server must enforce the DoD standards for password complexity and lifetime.
 
        .DESCRIPTION
            The Set-SQLInstanceLoginPasswordPolicies cmdlet enables the password policy and password expiration options on every SQL Login.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER IncludeSA
            Specifies whether the built in SA account should also have these properties set.
 
        .EXAMPLE
            Set-SQLInstanceLoginPasswordPolicies -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Enables the password policy and expiration on every SQL login on the specified instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Instance V1R0
            STIG ID
                SQL4-00-038900
            Rule ID
                SQL4-00-038900_rule
            Vuln ID
                SQL4-00-038900
            Severity
                CAT II
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter()]
        [switch]$IncludeSA,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Setting SQL Login Password Policy and Expiration."
        Write-Verbose -Message "STIG ID: SQL4-00-038900"
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if ($IncludeSA) {
            $SqlConnection.Logins | Where-Object {$_.LoginType -eq [Microsoft.SqlServer.Management.Smo.LoginType]::SqlLogin } | ForEach-Object {
                Write-Verbose -Message "Configuring password policy for $($_.Name)."
                $_.PasswordPolicyEnforced = $true
                $_.PasswordExpirationEnabled = $true
                $_.Alter()
            }
        }
        else {           
            $SqlConnection.Logins | Where-Object {$_.LoginType -eq [Microsoft.SqlServer.Management.Smo.LoginType]::SqlLogin -and $_.Sid -ne 0x01 } | ForEach-Object {
                Write-Verbose -Message "Configuring password policy for $($_.Name)."
                $_.PasswordPolicyEnforced = $true
                $_.PasswordExpirationEnabled = $true
                $_.Alter()
            }
        }       
    }

    End {

    }
}

Function Set-SQLInstanceProtocols {
    <#
 
 
    .FUNCTIONALITY
        STIG ID SQL4-00-034200 Rule ID SQL4-00-034200_rule Vuln ID SQL4-00-034200
        Severity
            CAT II
    #>

    [CmdletBinding()]
    Param(

    )
}

Function Set-SQLInstanceDefaultTrace{
    <#
        .SYNOPSIS
            Enables or disables the default trace.
 
        .DESCRIPTION
            The Set-SQLInstanceDefaultTrace cmdlet enables or disables the default trace on the instance.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Enabled
            Specifies whether to enable or disable the default trace. This defaults to true.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Set-SQLInstanceDefaultTrace -ComputerName "SQL2014" -InstanceName "MyInstance" -Enabled $true
             
            Enables the default trace on the specified instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter()]
        [bool]$Enabled = $true,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        Write-Verbose -Message "Enabling Show Advanced Options."
        $Max = $SqlConnection.Configuration.ShowAdvancedOptions.Maximum
        $Min = $SqlConnection.Configuration.ShowAdvancedOptions.Minimum
        Write-Verbose -Message "ShowAdvancedOptions mininum: $Min maximum: $Max"
        $SqlConnection.Configuration.ShowAdvancedOptions.ConfigValue = $Max
        $SqlConnection.Configuration.Alter()
        Write-Verbose -Message "Setting default trace enabled to $Enabled."
        $SqlConnection.Configuration.DefaultTraceEnabled.ConfigValue = $Enabled
        $SqlConnection.Configuration.Alter()
        Write-Verbose -Message "Disabling Show Advanced Options."
        $SqlConnection.Configuration.ShowAdvancedOptions.ConfigValue = $Min
        $SqlConnection.Configuration.Alter()
    }

    End {
        if ($PassThru) {
            Write-Output $SqlConnection.Configuration.DefaultTraceEnabled
        }
    }
}

Function Add-SQLInstanceLogin {
    <#
        .SYNOPSIS
            Adds a new login to the SQL Instance.
 
        .DESCRIPTION
            The Add-SQLInstanceLogin cmdlet creates a new Windows User, Group, or SQL Login.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
            The name of the login to create.
 
        .PARAMETER LoginType
            The type of login to create, either WindowsUser (also used for groups) or SqlLogin. This defaults to WindowsUser.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Add-SQLInstanceLogin -ComputerName "SQL2014" -InstanceName "MyInstance" -Name "Contoso\UG-SQL-Users" -LoginType WindowsUser
             
            Creates a new login for the UG-SQL-Users group on the instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.Login
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Position=0,Mandatory=$true)]
        [System.String]$Name,
        [Parameter(Position=1)]
        [ValidateSet("WindowsUser", "SqlLogin")]
        [System.String]$LoginType = "WindowsUser",
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        [System.String[]]$Logins = $SqlConnection.Logins | Where-NotMatchIn -Matches $script:IgnoreNames -Property "Name" | Select-Object -ExpandProperty Name

        if ($Name -inotin $Logins) {
            Write-Verbose -Message "Creating login $Name."
            [Microsoft.SqlServer.Management.Smo.Login]$NewLogin = New-Object Microsoft.SqlServer.Management.Smo.Login($SqlConnection, $Name)
            $NewLogin.DefaultDatabase = "master";

            switch ($LoginType.ToLower()) {
                "windowsuser" {
                    $NewLogin.LoginType = [Microsoft.SqlServer.Management.Smo.LoginType]::WindowsUser
                    break
                }
                "sqllogin" {
                    $NewLogin.LoginType = [Microsoft.SqlServer.Management.Smo.LoginType]::SqlLogin
                    break
                }
                default {
                    throw "Could not determine login type $LoginType."
                }
            }

            try {
                $NewLogin.Create()
            }
            catch [Exception] {
                if ($_.Exception.InnerException -ne $null) {
                    $Exception = $_.Exception
                    while ($Exception.InnerException -ne $null) {                      
                        $Exception = $Exception.InnerException
                    }

                    throw $Exception
                }
                else {
                    throw $_.Exception
                }
            }
        }
        else {
            Write-Verbose -Message "$Name login already exists."
            [Microsoft.SqlServer.Management.Smo.Login]$NewLogin = $SqlConnection.Logins | Where-Object { $_.Name -ieq $Name } | Select-Object -First 1
        }
    }

    End {
        if ($PassThru) {
            Write-Output $NewLogin
        }
    }
}

Function Add-SQLInstanceServerRole {
    <#
        .SYNOPSIS
            Adds a new role to the SQL Instance.
 
        .DESCRIPTION
            The Add-SQLInstanceRole cmdlet creates a Server Role.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
            The name of the role to create.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Add-SQLInstanceRole -ComputerName "SQL2014" -InstanceName "MyInstance" -Name "NEW_ROLE"
             
            Creates a new role named NEW_ROLE.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.ServerRole
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Position=0,Mandatory=$true)]
        [System.String]$Name,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        [System.String[]]$Roles = $SqlConnection.Roles | Where-Object {$_.IsFixedRole -eq $false } | Select-Object -ExpandProperty Name

         if ($Name -inotin $Roles) {
            Write-Verbose -Message "Creating role $Name"
            [Microsoft.SqlServer.Management.Smo.ServerRole]$NewRole = New-Object Microsoft.SqlServer.Management.Smo.ServerRole($SqlConnection, $Name)
            
            try {
                $NewRole.Create()
            }
            catch [Exception] {
                if ($_.Exception.InnerException -ne $null) {
                    $Exception = $_.Exception
                    while ($Exception.InnerException -ne $null) {                      
                        $Exception = $Exception.InnerException
                    }

                    throw $Exception
                }
                else {
                    throw $_.Exception
                }
            }
        }
        else {
            Write-Verbose -Message "$Name role already exists."
            [Microsoft.SqlServer.Management.Smo.ServerRole]$NewRole = $SqlConnection.Roles | Where-Object {$_.Name -ieq $Name } | Select-Object -First 1
        }
    }

    End {
        if ($PassThru) {
            Write-Output $NewRole
        }
    }
}

Function New-SQLInstanceJobCategory {
    <#
        .SYNOPSIS
            Adds a new job category to the SQL Instance.
 
        .DESCRIPTION
            The Add-SQLInstanceJobCategory cmdlet creates a new job category of use with SQL Agent jobs.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
            The name of the job category to create.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            New-SQLInstanceJobCategory -ComputerName "SQL2014" -InstanceName "MyInstance" -Name "STIG Audits"
             
            Creates a new job category named STIG Audits.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.Agent.JobCategory
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Position=0,Mandatory=$true)]
        [System.String]$Name,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }


        if (($SqlConnection.JobServer.JobCategories | Where-Object {$_.Name -ieq $Name}) -eq $null) {
            Write-Verbose -Message "Job category does not already exist, creating it."
            [Microsoft.SqlServer.Management.Smo.Agent.JobCategory]$Category = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Agent.JobCategory($SqlConnection.JobServer, $Name)
            $Category.CategoryType = [Microsoft.SqlServer.Management.Smo.Agent.CategoryType]::LocalJob
        
            try {
                $Category.Create()
            }
            catch [Exception] {
                if ($_.Exception.InnerException -ne $null) {
                    $Exception = $_.Exception
                    while ($Exception.InnerException -ne $null) {                      
                        $Exception = $Exception.InnerException
                    }

                    throw $Exception
                }
                else {
                    throw $_.Exception
                }
            }
        }
        else {
            Write-Verbose -Message "Job category already exists, returning it."
            [Microsoft.SqlServer.Management.Smo.Agent.JobCategory]$Category = $SqlConnection.JobServer.JobCategories | Where-Object {$_.Name -ieq $Name} | Select-Object -First 1
        }
    }

    End {
        if ($PassThru) {
            Write-Output $Category
        }
    }
}

Function New-SQLAgentJob {
    <#
        .SYNOPSIS
            Adds a new SQL Agent Job.
 
        .DESCRIPTION
            The New-SQLAgentJob cmdlet creates a new job.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
            The name of the job to create.
 
        .PARAMETER Description
            The description of the new job.
 
        .PARAMETER EmailLevel
            Defines when an operator is notified by email, the options are: Always, Never, OnFailure, OnSuccess. This defaults to Never.
 
        .PARAMETER EventLogLevel
            Defines when an event is written to the event log, the options are: Always, Never, OnFailure, OnSuccess. This defaults to Never.
 
        .PARAMETER NetSendLevel
            Defines when an operator is notified by NetSend, the options are: Always, Never, OnFailure, OnSuccess. This defaults to Never.
 
        .PARAMETER PageLevel
            Defines when an operator is notified by page, the options are: Always, Never, OnFailure, OnSuccess. This defaults to Never.
 
        .PARAMETER DeleteLevel
            Defines is the job is deleted after it runs, the options are: Always, Never, OnFailure, OnSuccess. This defaults to Never.
 
        .PARAMETER OperatorToEmail
            The name of the operator to send emails to.
 
        .PARAMETER OperatorToPage
            The name of the operator to page.
         
        .PARAMETER OperatorToNetSend
            The name of the operator to NetSend.
 
        .PARAMETER Owner
            The name of the SQL Login that should be the owner of the job. This defaults to the sa account.
 
        .PARAMETER JobCategory
            The name of the job category the job should be a part of. This defaults to the job category with ID 0.
 
        .EXAMPLE
            $FunctionCheckJob = New-SQLAgentJob -ComputerName "SQL2014 -InstanceName "MyInstance" `
                -Name "Alter_Function_Audit" `
                -Description "Daily audit to make sure no modifications to functions have been made." `
                -EmailLevel Always `
                -EventLogLevel OnFailure `
                -NetSendLevel Never `
                -PageLevel Never `
                -DeleteLevel Never `
                -OperatorToEmail "Database Administrators" `
                -JobCategory "STIG Audits"
 
            Creates a new job that will be used to check function modifications. This job still requires a job step and schedule to be created.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.Agent.Job
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Position=0,Mandatory=$true)]
        [System.String]$Name,
        [Parameter(Position=1)]
        [System.String]$Description = [System.String]::Empty,
        [Parameter()]
        [ValidateSet("Always", "Never", "OnFailure", "OnSuccess")]
        [System.String]$EmailLevel = "Never",
        [Parameter()]
        [ValidateSet("Always", "Never", "OnFailure", "OnSuccess")]
        [System.String]$EventLogLevel = "Never",
        [Parameter()]
        [ValidateSet("Always", "Never", "OnFailure", "OnSuccess")]
        [System.String]$NetSendLevel = "Never",
        [Parameter()]
        [ValidateSet("Always", "Never", "OnFailure", "OnSuccess")]
        [System.String]$PageLevel = "Never",
        [Parameter()]
        [ValidateSet("Always", "Never", "OnFailure", "OnSuccess")]
        [System.String]$DeleteLevel = "Never",
        [Parameter()]
        [System.String]$OperatorToEmail = [System.String]::Empty,
        [Parameter()]
        [System.String]$OperatorToPage = [System.String]::Empty,
        [Parameter()]
        [System.String]$OperatorToNetSend = [System.String]::Empty,
        [Parameter()]
        [System.String]$Owner = [System.String]::Empty,
        [Parameter()]
        [System.String]$JobCategory = [System.String]::Empty
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if (($SqlConnection.JobServer.Jobs | Where-Object {$_.Name -ieq $Name}) -eq $null) {

            [Microsoft.SqlServer.Management.Smo.Agent.Job]$Job = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Agent.Job($SqlConnection.JobServer, $Name)
        
            if (![System.String]::IsNullOrEmpty($Description)) {
                $Job.Description = $Description
            }

            [bool]$IgnoreCase = $true

            if (![System.String]::IsNullOrEmpty($OperatorToEmail)) {
                [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]$Action = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never

                if ([System.Enum]::TryParse($EmailLevel, $IgnoreCase, [ref]$Action)) {
                    $Job.EmailLevel = $Action
                    $Job.OperatorToEmail = $OperatorToEmail
                }
                else {
                    $Job.EmailLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
                }
            }
            else {
                $Job.EmailLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
            }

            if (![System.String]::IsNullOrEmpty($OperatorToPage)) {

                if ([System.Enum]::TryParse($PageLevel, $IgnoreCase, [ref]$Action)) {
                    $Job.PageLevel = $Action
                    $Job.OperatorToPage = $OperatorToPage
                }
                else {
                    $Job.PageLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
                }
            }
            else {
                $Job.PageLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
            }

            if (![System.String]::IsNullOrEmpty($OperatorToNetSend)) {

                if ([System.Enum]::TryParse($NetSendLevel, $IgnoreCase, [ref]$Action)) {
                    $Job.NetSendLevel = $Action
                    $Job.OperatorToNetSend = $OperatorToNetSend
                }
                else {
                    $Job.NetSendLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
                }
            }
            else {
                $Job.NetSendLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
            }

            if ([System.Enum]::TryParse($EventLogLevel, $IgnoreCase, [ref]$Action)) {
                $Job.EventLogLevel = $Action
            }
            else {
                $Job.EventLogLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::OnFailure
            }
        
            if ([System.Enum]::TryParse($DeleteLevel, $IgnoreCase, [ref]$Action)) {
                $Job.DeleteLevel = $Action
            }
            else {
                $Job.DeleteLevel = [Microsoft.SqlServer.Management.Smo.Agent.CompletionAction]::Never
            }        
        
            if (![System.String]::IsNullOrEmpty($JobCategory)) {
                [Microsoft.SqlServer.Management.Smo.Agent.JobCategory]$Category = $SqlConnection.JobServer.JobCategories | Where-Object {$_.Name -ieq $JobCategory} | Select-Object -First 1
            }

            if ($Category -eq $null -or [System.String]::IsNullOrEmpty($JobCategory)) {
                [Microsoft.SqlServer.Management.Smo.Agent.JobCategory]$Category = $SqlConnection.JobServer.JobCategories | Where-Object {$_.ID -eq 0} | Select-Object -First 1
            }

            $Job.Category = $Category.Name

            if (![System.String]::IsNullOrEmpty($Owner)) {
                $Job.OwnerLoginName = $Owner
            }
            else {
                #Sets the owner to the built in sa account
                $Job.OwnerLoginName = $SqlConnection.Logins | Where-Object {$_.Sid -eq 0x01} | Select-Object -First 1 -ExpandProperty Name
            }

            try {
                $Job.Create()
            }
            catch [Exception] {
                if ($_.Exception.InnerException -ne $null) {
                    $Exception = $_.Exception
                    while ($Exception.InnerException -ne $null) {                      
                        $Exception = $Exception.InnerException
                    }

                    throw $Exception
                }
                else {
                    throw $_.Exception
                }
            }
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Agent.Job]$Job = $SqlConnection.JobServer | Where-Object {$_.Name -ieq $Name} | Select-Object -First 1
        }
    }

    End {
        Write-Output $Job
    }
}

Function New-SQLAgentJobStep {
    <#
        .SYNOPSIS
            Adds a new job step to a specified job.
 
        .DESCRIPTION
            The Add-SQLAgentJobStep cmdlet creates a new job step as part of an existing job.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Job
            The Job this step is being created for.
 
        .PARAMETER Name
            The name of the job step to create.
 
        .PARAMETER SubSystem
            The subsystem that will be used to run the step, the options are: "ActiveScripting", "AnalysisCommand", "AnalysisQuery", "CmdExec", "Distribution", "LogReader", "Merge", "PowerShell", "QueueReader", "Snapshot", "Ssis", "TransactSql".
 
            This defaults to TransactSql.
 
        .PARAMETER RetryAttempts
            The maximum number of times a job step is retried before it is returned with a completion status of failure. This defaults to 0.
 
        .PARAMETER RetryInterval
            The number of minutes that Microsoft SQL Server Agent waits before trying to execute a job step that has previously failed. This defaults to 0.
 
        .PARAMETER OSRunPriority
            The execution thread scheduling for job steps executing operating system tasks. This defaults to 0.
 
        .PARAMETER DatabaseName
            The name of the database to which the job step command execution is confined. This defaults to master.
 
        .PARAMETER DatabaseUser
            The database user account that the job step assumes when executing the command string.
     
        .PARAMETER OnFailAction
            The action to take when the job step finishes execution with failure. The options are "GoToNextStep", "GoToStep", "QuitWithFailure", "QuitWithSuccess". This defaults to QuitWithFailure.
 
        .PARAMETER OnSuccessAction
            The action to take when the job step finishes execution with success. The options are "GoToNextStep", "GoToStep", "QuitWithFailure", "QuitWithSuccess". This defaults to QuitWithSuccess.
 
        .PARAMETER Command
            The command execution string for the job step.
 
        .EXAMPLE
            $FunctionCheckJobStep = New-SQLAgentJobStep -ComputerName "SQL2014" -InstanceName "MyInstance" `
                -Name "Alter_Function_Audit_Step" `
                -Job $FunctionCheckJob `
                -DatabaseName "master" `
                -OnFailAction QuitWithFailure `
                -OnSuccessAction QuitWithSuccess `
                -RetryAttempts 0 `
                -RetryInterval 0 `
                -OSRunPriority 0 `
                -SubSystem TransactSql `
                -Command "PRINT('Ran job');"
             
            Creates a job step that prints the text "Ran job".
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.Agent.JobStep
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithJob")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithJob")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnectionWithName",Mandatory=$true,Position=0)]
        [Parameter(ParameterSetName="ExistingConnectionWithJob",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(ParameterSetName="ExistingConnectionWithName",Mandatory=$true,Position=1)]
        [Parameter(ParameterSetName="NewConnectionWithName",Mandatory=$true,Position=1)]
        [System.String]$JobName,
        [Parameter(ParameterSetName="NewConnectionWithJob",Mandatory=$true,Position=1)]
        [Parameter(ParameterSetName="ExistingConnectionWithJob",Mandatory=$true,Position=1)]
        $Job,
        [Parameter()]
        [System.String]$Name,
        [Parameter()]
        [ValidateSet("ActiveScripting", "AnalysisCommand", "AnalysisQuery", "CmdExec", "Distribution", "LogReader", "Merge", "PowerShell", "QueueReader", "Snapshot", "Ssis", "TransactSql")]
        [System.String]$SubSystem = "TransactSql",
        [Parameter()]
        [ValidateScript({$_ -ge 0})]
        [System.Int32]$RetryAttempts = 0,
        [Parameter()]
        [ValidateScript({$_ -ge 0})]
        [System.Int32]$RetryInterval = 0,
        [Parameter()]
        [ValidateScript({$_ -ge 0})]
        [System.Int32]$OSRunPriority = 0,
        [Parameter()]
        [ValidateScript({![System.String]::IsNullOrEmpty($_)})]
        [System.String]$DatabaseName = "master",
        [Parameter()]
        [System.String]$DatabaseUser,
        [Parameter()]
        [ValidateSet("GoToNextStep", "GoToStep", "QuitWithFailure", "QuitWithSuccess")]
        [System.String]$OnFailAction = "QuitWithFailure",
        [Parameter()]
        [ValidateSet("GoToNextStep", "GoToStep", "QuitWithFailure", "QuitWithSuccess")]
        [System.String]$OnSuccessAction = "QuitWithSuccess",
        [Parameter(Mandatory=$true)]
        [System.String]$Command
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        if ($Job -ne $null -and $Job.GetType() -ne [Microsoft.SqlServer.Management.Smo.Agent.Job]) {
            throw "Job parameter must be an existing Microsoft.SqlServer.Management.Smo.Agent.Job object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -like "ExistingConnection*") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if ($PSCmdlet.ParameterSetName -like "*WithName") {
            [Microsoft.SqlServer.Management.Smo.Job]$Job = $SqlConnection.JobServer.Jobs | Where-Object {$_.Name -ieq $JobName} | Select-Object -First 1
            
            if ($Job -eq $null) {
                throw "$JobName does not match any existing jobs."
            }
        }

        [Microsoft.SqlServer.Management.Smo.Agent.JobStep]$JobStep = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Agent.JobStep($Job, $Name)

        [Microsoft.SqlServer.Management.Smo.Agent.AgentSubSystem]$SubSystemObject = [Microsoft.SqlServer.Management.Smo.Agent.AgentSubSystem]::TransactSql

        if ([System.Enum]::TryParse($SubSystem, [ref]$SubSystemObject)) {
            $JobStep.SubSystem = $SubSystemObject
        }
        else {
            $JobStep.SubSystem = [Microsoft.SqlServer.Management.Smo.Agent.AgentSubSystem]::TransactSql
        }

        [Microsoft.SqlServer.Management.Smo.Agent.StepCompletionAction]$Action = [Microsoft.SqlServer.Management.Smo.Agent.StepCompletionAction]::QuitWithFailure

        if ([System.Enum]::TryParse($OnFailAction, [ref]$Action)) {
            $JobStep.OnFailAction = $Action
        }
        else {
            $JobStep.OnFailAction = [Microsoft.SqlServer.Management.Smo.Agent.StepCompletionAction]::QuitWithFailure
        }

        if ([System.Enum]::TryParse($OnSuccessAction, [ref]$Action)) {
            $JobStep.OnSuccessAction = $Action
        }
        else {
            $JobStep.OnSuccessAction = [Microsoft.SqlServer.Management.Smo.Agent.StepCompletionAction]::QuitWithSuccess
        }

        $JobStep.DatabaseName = $DatabaseName

        if (![System.String]::IsNullOrEmpty($DatabaseUser)) {
            $JobStep.DatabaseUser = $DatabaseUser
        }

        $JobStep.OSRunPriority = $OSRunPriority
        $JobStep.RetryAttempts = $RetryAttempts
        $JobStep.RetryInterval = $RetryInterval
        $JobStep.Command = $Command

        try {
            $JobStep.Create()
        }
        catch [Exception] {
            if ($_.Exception.InnerException -ne $null) {
                $Exception = $_.Exception
                while ($Exception.InnerException -ne $null) {                      
                    $Exception = $Exception.InnerException
                }

                throw $Exception
            }
            else {
                throw $_.Exception
            }
        }
    }

    End {
        Write-Output $JobStep
    }

}

<#
    WeekDays.Sunday = 1
    WeekDays.Monday = 2
    WeekDays.Tuesday = 4
    WeekDays.Wednesday = 8
    WeekDays.Thursday = 16
    WeekDays.Friday = 32
    WeekDays.Saturday = 64
    WeekDays.WeekDays = 62
    WeekDays.WeekEnds = 65
    WeekDays.EveryDay = 127
#>


Function New-SQLAgentJobSchedule {
    <#
        .SYNOPSIS
            Adds a new schedule to a specified job.
 
        .DESCRIPTION
            The Add-SQLAgentJobSchedule cmdlet creates a new schedule to run a specified job.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Job
            The Job this schedule is being created for.
 
        .PARAMETER JobName
            The name of the job this schedule is being created for.
 
        .PARAMETER Name
            The name of the schedule to be created.
 
        .PARAMETER ActiveEndDate
            The date and time when the schedule ends.
 
        .PARAMETER ActiveEndTimeOfDay
            The time when the job schedule stops for the day.
 
        .PARAMETER ActiveStartDate
            The date and time when the schedule starts. This defaults to now.
 
        .PARAMETER ActiveStartTimeOfDay
            The time when the job schedule starts for the day.
 
        .PARAMETER FrequencyInterval
            The frequency interval, which determines how often the job is scheduled to run. https://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.agent.jobschedule.frequencyinterval.aspx
 
        .PARAMETER FreqeuncyType
            The way in which frequency is evaluated for the job schedule, whether it's one time only, or weekly, or when the processor is idle, for example. The options are: "AutoStart", "Daily", "Monthly", "MonthlyRelative", "OneTime", "OnIdle", "Unknown", "Weekly".
 
            This defaults to Unknown.
             
        .EXAMPLE
            $FunctionCheckSchedule = New-SQLAgentJobSchedule -ComputerName "SQL2014" -InstanceName "MyInstance" `
                -Name "Daily" `
                -Job $FunctionCheckJob `
                -ActiveStartTimeOfDay (New-Object System.TimeSpan(2, 0, 0)) `
                -FrequencyType Daily `
                -FrequencyInterval 1
             
            Creates a schedule that runs daily at 2AM.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.Agent.JobSchedule
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithJob")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnectionWithName")]
        [Parameter(ParameterSetName="NewConnectionWithJob")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnectionWithName",Mandatory=$true,Position=0)]
        [Parameter(ParameterSetName="ExistingConnectionWithJob",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Position=2,Mandatory=$true)]
        [System.String]$Name,
        [Parameter(ParameterSetName="ExistingConnectionWithName",Mandatory=$true,Position=1)]
        [Parameter(ParameterSetName="NewConnectionWithName",Mandatory=$true,Position=1)]
        [System.String]$JobName,
        [Parameter(ParameterSetName="NewConnectionWithJob",Mandatory=$true,Position=1)]
        [Parameter(ParameterSetName="ExistingConnectionWithJob",Mandatory=$true,Position=1)]
        $Job,
        [Parameter()]
        [System.DateTime]$ActiveEndDate = [System.DateTime]::MinValue,
        [Parameter()]
        [System.TimeSpan]$ActiveEndTimeOfDay = [System.TimeSpan]::Zero,
        [Parameter()]
        [System.DateTime]$ActiveStartDate = [System.DateTime]::Now,
        [Parameter(Mandatory=$true)]
        [System.TimeSpan]$ActiveStartTimeOfDay,
        [Parameter()]
        [System.Int32]$FrequencyInterval,
        [Parameter()]
        [ValidateSet("AutoStart", "Daily", "Monthly", "MonthlyRelative", "OneTime", "OnIdle", "Unknown", "Weekly")]
        [System.String]$FrequencyType = "Unknown"
        
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        if ($Job -ne $null -and $Job.GetType() -ne [Microsoft.SqlServer.Management.Smo.Agent.Job]) {
            throw "Job parameter must be an existing Microsoft.SqlServer.Management.Smo.Agent.Job object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -like "ExistingConnection*") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if ($PSCmdlet.ParameterSetName -like "*WithName") {
            [Microsoft.SqlServer.Management.Smo.Agent.Job]$Job = $SqlConnection.JobServer.Jobs | Where-Object {$_.Name -ieq $JobName} | Select-Object -First 1
            
            if ($Job -eq $null) {
                throw "$JobName does not match any existing jobs."
            }
        }

        [Microsoft.SqlServer.Management.Smo.Agent.JobSchedule]$Schedule = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Agent.JobSchedule($Job, $Name)

        [Microsoft.SqlServer.Management.Smo.Agent.FrequencyTypes]$Freq = [Microsoft.SqlServer.Management.Smo.Agent.FrequencyTypes]::Unknown

        if ([System.Enum]::TryParse($FrequencyType, [ref]$Freq)) {
            $Schedule.FrequencyTypes = $Freq
        }
        else {
            throw "Could not determine the frequency type given by $FrequencyType."
        }

        $Schedule.ActiveStartDate = $ActiveStartDate
        $Schedule.ActiveStartTimeOfDay = $ActiveStartTimeOfDay
        if ($ActiveEndDate -gt [System.DateTime]::MinValue -and $ActiveEndTimeOfDay -ne $null) {
            $Schedule.ActiveEndDate = $ActiveEndDate
            $Schedule.ActiveEndTimeOfDay = $ActiveEndTimeOfDay
        }
            
        $Schedule.FrequencyInterval = $FrequencyInterval

        try {
            $Schedule.Create()
        }
        catch [Exception] {
            if ($_.Exception.InnerException -ne $null) {
                $Exception = $_.Exception
                while ($Exception.InnerException -ne $null) {                      
                    $Exception = $Exception.InnerException
                }

                throw $Exception
            }
            else {
                throw $_.Exception
            }
        }
    }

    End {
        Write-Output $Schedule
    }
}

Function Set-SQLDatabaseTrustworthy {
    <#
        .SYNOPSIS
            Sets the trustworthy setting on a database.
 
        .DESCRIPTION
            The Set-SQLDatabaseTrustworthy cmdlet modifies the trustworthy setting. The SQL Server instance uses each database's TRUSTWORTHY property to guard against tampering that could enable
            unwarranted privilege escalation. When TRUSTWORTHY is 0/False/Off, SQL Server prevents the database from accessing resources in other databases.
            When TRUSTWORTHY is 1/True/On, SQL Server permits access to other databases (subject to other protections). SQL Server sets TRUSTWORTHY OFF when it creates a new database.
            SQL Server forces TRUSTWORTHY OFF, irrespective of its prior value, when an existing database is attached to it, to address the possibility that an adversary may have tampered with the database, introducing malicious code.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER DatabaseName
            The name of the database to modify.
 
        .PARAMETER Enabled
            Specifies whether this setting should be enabled or disabled.
 
        .PARAMTER PassThru
            Returns an object representing the item property. By default, this cmdlet does not generate any output.
 
        .EXAMPLE
            Set-SQLDatabaseTrustworthy -ComputerName "SQL2014" -InstanceName "MyInstance" -DatabaseName "master" -Enabled $false
             
            Sets the trustworthy setting on the master database to disabled.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft SQL Server 2014 Datavase V1R0
            STIG ID
                SQL4-00-015610
            Rule ID
                SQL4-00-015610_rule
            Vuln ID
                SQL4-00-015610
            Severity
                CAT II
 
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Mandatory=$true)]
        [System.String]$DatabaseName,
        [Parameter()]
        [System.Boolean]$Enabled = $false,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
        
        Write-Verbose -Message "Setting Database Trustworthy."
        Write-Verbose -Message "STIG ID: SQL4-00-015610"
    }

    Process {
        if ($DB.Name -eq "msdb" -and $Enabled -eq $false) {
            Write-Warning -Message "msdb database is required to be set to trustworthy, this cmdlet will not modify this setting."
        }
        else {
            if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
                [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
            }
            else {
                [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
            }

            [Microsoft.SqlServer.Management.Smo.Database]$DB = $SqlConnection.Databases | Where-Object {$_.Name -eq $DatabaseName} | Select-Object -First 1
            $DB.Trustworthy = $Enabled
        }
    }

    End {
        if ($PassThru) {
            Write-Output $DB.Trustworthy
        }
    }
}

Function Get-SQLInstanceServerRoleMembership {
    <#
        .SYNOPSIS
            Gets the Server Role membership of a specified principal.
 
        .DESCRIPTION
            The Get-SQLInstanceServerRoleMembership cmdlet gets all of the Server Roles that a principal is a member of.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER PrincipalName
            The name of the principal to get role membership of.
 
        .PARAMETER OnlyFixedRoles
            Specifies is only the membership in fixed roles should be returned as a result.
 
        .EXAMPLE
            Get-SQLInstanceServerRoleMembership -ComputerName "SQL2014" -InstanceName "MyInstance" -PrincipalName "sa"
             
            Gets all of the server role membership for the sa account.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.ServerRole[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Mandatory=$true)]
        [System.String]$PrincipalName,
        [Parameter()]
        [switch]$OnlyFixedRoles
    
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        Write-Verbose -Message "Getting roles for $PrincipalName."
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        [Microsoft.SqlServer.Management.Smo.ServerRole[]]$Roles = @()

        if ($OnlyFixedRoles) {
            foreach($Role in ($SqlConnection.Roles | Where-Object { $_.IsFixedRole -eq $true } )) {
                Write-Verbose "Checking role $($Role.Name)."
                if ($Role.EnumMemberNames() -icontains $PrincipalName) {
                    $Roles += $Role
                }
            }
        }
        else {
            foreach($Role in ($SqlConnection.Roles | Where-Object { $_.IsFixedRole -eq $true } )) {
                Write-Verbose -Message "Checking role $($Role.Name)."
                if ($Role.EnumMemberNames() -icontains $PrincipalName) {
                    $Roles += $Role
                }
            }
        }
    }

    End {
        Write-Output $Roles
    }
}

Function New-SQLDatabaseDDLTrigger {
    <#
        .SYNOPSIS
            Creates a new database DDL trigger.
 
        .DESCRIPTION
            The New-SQLDatabaseDDLTrigger cmdlet creates a new DDL trigger on a specified database.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER Name
             Specifies the name of the DDL trigger
 
        .PARAMETER DatabaseName
            Specifies the database on which the database DDL trigger is created.
 
        .PARAMETER CommandText
            Specifies the text body that is used to define the DDL trigger.
 
        .PARAMETER EventSet
            A Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet object specifying the events that should be triggered on.
 
        .PARAMETER ImplementationType
            The implementation type for the database data definition language (DDL) trigger. The options are SqlClr or TransactSql. This defaults to TransactSql.
 
        .PARAMETER Owner
            The database user that is the context used when the database DDL trigger executes. This defaults to the Sql Agent service account.
 
        .EXAMPLE
            [Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet]$EventSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet
            $EventSet.CreateProcedure = $true
 
            New-SQLDatabaseDDLTrigger -ComputerName "SQL2014" -InstanceName "MyInstance" `
                                    -Name "AuditModificationsTrigger" `
                                    -DatabaseName "master" `
                                    -CommandText "PRINT('Procedure created.')" `
                                    -EventSet $EventSet `
                                    -ImplementationType TransactSql
             
            Creates a DDL trigger on the master database that fires anytime a stored procedure is created on that database.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.DatabaseDdlTrigger
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer,
        [Parameter(Mandatory=$true)]
        [System.String]$Name,
        [Parameter(Mandatory=$true)]
        [System.String]$DatabaseName,
        [Parameter(Mandatory=$true)]
        [System.String]$CommandText,
        [Parameter(Mandatory=$true)]
        $EventSet,
        [Parameter()]
        [Validateset("SqlClr", "TransactSql")]
        [System.String]$ImplementationType = "TransactSql",
        [Parameter()]
        [System.String]$Owner
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }

        if ($EventSet -ne $null -and $EventSet.GetType() -ne [Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet]) {
            throw "EventSet parameter must be an existing Microsoft.SqlServer.Management.Smo.DatabaseDdlTriggerEventSet object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        [Microsoft.SqlServer.Management.Smo.Database]$DB = $SqlConnection.Databases | Where-Object {$_.Name -ieq $DatabaseName} | Select-Object -First 1
        if ($DB -ne $null) {
            if (($DB.Triggers | Where-Object {$_.Name -ieq $Name}) -eq $null) {
                Write-Verbose -Message "Trigger does not already exist, creating it."

                [Microsoft.SqlServer.Management.Smo.DatabaseDdlTrigger]$Trigger = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseDdlTrigger($DB, $Name, $EventSet, $CommandText)
                [Microsoft.SqlServer.Management.Smo.ImplementationType]$Impl = [Microsoft.SqlServer.Management.Smo.ImplementationType]::TransactSql

                if([System.Enum]::TryParse($ImplementationType, [ref]$Impl)) {
                    $Trigger.ImplementationType = $Impl
                }
                else {
                    $Trigger.ImplementationType = [Microsoft.SqlServer.Management.Smo.ImplementationType]::TransactSql
                }

                if ([System.String]::IsNullOrEmpty($Owner)) {
                    $Owner = $SqlConnection.JobServer.ServiceAccount
                }

                $Trigger.TextMode = $false
                $Trigger.ExecutionContext = [Microsoft.SqlServer.Management.Smo.ExecutionContext]::Owner
                $Trigger.ExecutionContextUser = $Owner

                try {
                    $Trigger.Create()
                }
                catch [Exception] {
                    if ($_.Exception.InnerException -ne $null) {
                        $Exception = $_.Exception
                        while ($Exception.InnerException -ne $null) {                      
                            $Exception = $Exception.InnerException
                        }

                        throw $Exception
                    }
                    else {
                        throw $_.Exception
                    }
                }
            }
            else {
                Write-Verbose -Message "Trigger already exists in $($DB.Name), returning it."
                $Trigger = $DB.Triggers | Where-Object {$_.Name -ieq $Name} | Select-Object -First 1
            }
        }
        else {
            Write-Error -Message "Database $DatabaseName could not be found."
        }
    }

    End {
        if ($Trigger -ne $null) {
            Write-Output $Trigger
        }
    }
}

Function Get-SQLInstanceErrorLogPath {
    <#
        .SYNOPSIS
            Gets the SQL Instance error log path.
 
        .DESCRIPTION
            The Get-SQLInstanceErrorLogPath cmdlet gets the default error log path for the instance.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .EXAMPLE
            Get-SQLInstanceErrorLogPath -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Gets the error log path for the MyInstance SQL Instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true,Position=0)]
        $SqlServer
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }
    }

    End {
        Write-Output $SqlConnection.ErrorLogPath
    }
}

#endregion

#region Utility Functions

Function Get-SQLServer {
    <#
        .SYNOPSIS
            Connects to a provided SQL instance with SMO.
 
        .DESCRIPTION
            The Get-SQLServer cmdlet connects to the specified SQL Server and Instance and provides a Microsoft.SqlServer.Management.Smo.Server object back to use for futher actions.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER Port
            The port the instance is running on. If this parameter is not defined, the cmdlet uses the SQL Browser service to identify the correct port.
 
        .PARAMETER InitialCatalog
            The initial database to connect to. This defaults to master.
 
        .PARAMETER Credential
            The credential to use to connect to the database if SQL Authentication is being used. Otherwise Windows Integrated Authentication with SSPI is used.
     
        .EXAMPLE
            $Server = Get-SQLServer -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Connects to the specified SQL Instance and provides back a server object.
 
        .INPUTS
            None
 
        .OUTPUTS
            Microsoft.SqlServer.Management.Smo.Server
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [System.String]$ComputerName = "localhost",
        [Parameter(Position=1)]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(Position=2)]
        [System.Int32]$Port = -1,
        [Parameter(Position=3)]
        [System.String]$InitialCatalog = "master",
        [Parameter(Position=4)]
        [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty
    )

    Begin {
        Import-SqlModule -LoadSMO

        if([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if([System.String]::IsNullOrEmpty($InitialCatalog)) {
            $InitialCatalog = "master"
        }
    }

    Process {
        [Microsoft.SqlServer.Management.Common.ServerConnection]$Connection = New-Object Microsoft.SqlServer.Management.Common.ServerConnection

        if ($InstanceName -ieq "MSSQLSERVER") {
            Write-Verbose -Message "Using the default SQL Instance."
            $Connection.ServerInstance = $ComputerName
            $ConnectionString = "Data Source = $ComputerName"
        }
        else {
            Write-Verbose -Message "Using a named SQL Instance, $InstanceName."
            $Connection.ServerInstance = "$ComputerName\$InstanceName"
            $ConnectionString = "Data Source = $ComputerName\$InstanceName"
        }

        if ($Port -gt 0) {
            Write-Verbose -Message "Using a defined port, $Port."
            $Connection.ServerInstance += ",$Port"
            $ConnectionString += ",$Port;"
        }
        else {
            Write-Verbose -Message "Using the SQL Browser service to determine the port."
            $ConnectionString += ";"
        }
        
        $Connection.DatabaseName = $InitialCatalog
        $ConnectionString += " Initial Catalog=$InitialCatalog;"

        if ($Credential -eq [System.Management.Automation.PSCredential]::Empty) {
            Write-Verbose -Message "Using SSPI Integrated Security."
            $ConnectionString += " Integrated Security=SSPI;"
            $Connection.LoginSecure = $true
        }
        else {
            Write-Verbose -Message "Using UserName and Password to connect."
            $Connection.LoginSecure = $false
            $Connection.Login = $Credential.UserName
            $Connection.SecurePassword = $Credential.Password

            $ConnectionString += " Integrated Security = false; User ID = $($Credential.UserName); Password=$(New-Object -TypeName System.String('*',$Credential.Password.Length));"
        }
        
        Write-Verbose -Message "Connection String:`n $ConnectionString"

        [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = New-Object Microsoft.SqlServer.Management.Smo.Server($Connection)
    }

    End {
        Write-Output $SqlConnection
    }
}

Function Get-SQLInstanceVersion {
    <#
        .SYNOPSIS
            Gets version information about the provided SQL instance.
 
        .DESCRIPTION
            The cmdlet gets version information about the provided SQL instance including version number, edition, and patch level.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .PARAMETER ShortVersionOnly
            Just returns the 2 digit number corresponding to the major SQL version.
             
        .INPUTS
            None
 
        .OUTPUTS
            System.Int
 
            System.Management.Automation.PSCustomObject
 
        .EXAMPLE
            Get-SQLInstanceVersion
 
            Gets the instance version for the MSSQLSERVER instance on localhost.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/20/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer,
        [Parameter()]
        [switch]$ShortVersionOnly
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        if ($ShortVersionOnly) {
            $Result = $SqlConnection.VersionMajor
        }
        else {        
            $Result = [PSCustomObject]@{
                Version = $SqlConnection.VersionString;
                ShortVersion = $SqlConnection.VersionMajor;
                Edition = $SqlConnection.Edition;
                ProductLevel = $SqlConnection.ProductLevel;
                OSVersion = $SqlConnection.OSVersion;
            }
        }
    }

    End {
        Write-Output $Result
    }
}

Function Get-SQLInstanceDetails {
    <#
        .SYNOPSIS
            Gets a core set of details about the provided SQL instance.
 
        .DESCRIPTION
            The cmdlet gets details about data directories, the instance version, the db enginer and sql agent service accounts and SIDs, and the installation path.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
             
        .INPUTS
            None
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
        .EXAMPLE
            Get-SQLInstanceDetails
 
            Gets the instance details for the MSSQLSERVER instance on localhost.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        $Version = Get-SQLInstanceVersion -ShortVersionOnly -SqlServer $SqlConnection
        $SqlServiceAccount = $SqlConnection.ServiceAccount
        $SqlAgentServiceAccount = $SqlConnection.JobServer.ServiceAccount
        $ServiceAccountSid = Get-AccountSid -UserName $SqlServiceAccount -SqlServer $SqlConnection    
        $SqlAgentAccountSid = Get-AccountSid -UserName $SqlAgentServiceAccount -SqlServer $SqlConnection

        $Directories = Get-SQLInstanceDataDirectories -SqlServer $SqlConnection

        $Result = [PSCustomObject]@{
            Version = $Version;
            DBEngineServiceAccount = $SqlServiceAccount;
            DBEngineServiceAccountSid = $ServiceAccountSid;
            SqlAgentServiceAccount = $SqlAgentServiceAccount;
            SqlAgentServiceAccountSid = $SqlAgentAccountSid;
        }

        $DataDirectories = @()
        foreach ($Directory in $Directories) {
            Add-Member -InputObject $Result -MemberType NoteProperty -Name $Directory -Value $Directories.$Directory
            $DataDirectories += $Directories.$Directory
        }

        Add-Member -InputObject $Result -MemberType NoteProperty -Name "DataDirectories" -Value $DataDirectories
    }

    End {
        Write-Output $Result
    }
}

Function Get-SQLInstanceDataDirectories {
    <#
        .SYNOPSIS
            Gets the data directories used by the SQL instance.
 
        .DESCRIPTION
            The cmdlet gets the default data path, default log path, the tempDB path (if different than the default data path), the backup directory, and the SQL root directory.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
             
        .INPUTS
            None
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
        .EXAMPLE
            Set-SQLInstanceDataDirectories
 
            Gets the data directories for the MSSQLSERVER instance on localhost.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }
    
    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        $DataRoot = $SqlConnection.RootDirectory
        $DefaultData = $SqlConnection.DefaultFile
        $DefaultLog = $SqlConnection.DefaultLog
        $BackupDirectory = $SqlConnection.BackupDirectory
        $InstallationDirectory = $SqlConnection.InstallDataDirectory
        $TempDBPath = $SqlConnection.Databases | Where-Object {$_.Name -eq "tempdb" } | Select-Object -ExpandProperty PrimaryFilePath
        $MasterDBPath = $SqlConnection.MasterDBPath
        $MasterDBLogPath = $SqlConnection.MasterDBLogPath
        $SqlProgramDir = [System.IO.Directory]::GetParent([System.IO.Directory]::GetParent($SqlConnection.InstallDataDirectory)).FullName

        $Directories = [PSCustomObject]@{SqlProgramDir = $SqlProgramDir; DefaultDataPath = $DefaultData; DefaultLogPath = $DefaultLog; BackupDirectory = $BackupDirectory; DataRootPath = $DataRoot; TempDBPath = $TempDBPath; MasterDBPath = $MasterDBPath; MasterDBLogPath = $MasterDBLogPath;}
    }

    End {
        Write-Output $Directories
    }
}

Function Get-SQLInstanceDefaultTraceFile {
    <#
        .SYNOPSIS
            Gets the most recent default trace file for the SQL Instance.
 
        .DESCRIPTION
            The Get-SQLInstanceDefaultTraceFile cmdlet gets the most recent default trace file path for the instance.
 
        .PARAMETER ComputerName
            The server running the SQL instance to be configured or the virtual resource name of a clustered SQL instance. This defaults to localhost.
 
        .PARAMETER InstanceName
            The name of the instance to be configured, this defaults to the default instance name of MSSQLSERVER.
     
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object.
 
        .EXAMPLE
            Get-SQLInstanceDefaultTraceFile -ComputerName "SQL2014" -InstanceName "MyInstance"
             
            Gets the default trace file path for the MyInstance SQL Instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$ComputerName = "localhost",
        [Parameter(ParameterSetName="NewConnection")]
        [System.String]$InstanceName = "MSSQLSERVER",
        [Parameter(ParameterSetName="ExistingConnection",Mandatory=$true)]
        $SqlServer
    )

    Begin {
        Import-SqlModule -LoadSMO

        if ([System.String]::IsNullOrEmpty($InstanceName)) {
            $InstanceName = "MSSQLSERVER"
        }

        if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
            throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
        }
    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "ExistingConnection") {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = $SqlServer
        }
        else {
            [Microsoft.SqlServer.Management.Smo.Server]$SqlConnection = Get-SQLServer -ComputerName $ComputerName -InstanceName $InstanceName
        }

        [Microsoft.SqlServer.Management.Smo.Database]$Master = $SqlConnection.Databases | Where-Object {$_.Name -eq "master"} | Select-Object -First 1
        $Data = $Master.ExecuteWithResults("SELECT TOP 1 path FROM sys.traces WHERE is_default = 1 ORDER BY last_event_time DESC")
        $TraceFilePath = $Data.Tables[0] | Select-Object -ExpandProperty Path
    }

    End {
        Write-Output $TraceFilePath
    }
}

Function Get-SQLInstanceAuditCommandText {
    <#
        .SYNOPSIS
            Gets the command text to be used with creating Agent jobs to audit modifications to Functions, Triggers, and Stored Procedures.
 
        .DESCRIPTION
            The Get-SQLInstanceAuditCommandText cmdlet creates the T-SQL commands run by a job to audit CREATE, ALTER, and DROP commands issued against Functions, Triggers, and Stored Procedures.
 
            The text reviews the default trace log reviewing specific event Ids for these actions. It collects the information and sends an HTML formatted email to the email addresses in the specified Operator name.
 
        .PARAMETER DaysDiff
            The number of days in the trace file to be reviewed. This defaults to 1.
 
        .PARAMETER JobName
            The name of the job that will be running the command. This is only used as information in the HTML body.
 
        .PARAMETER OperatorToEmail
            The name of the Operator whose email addresses will be used with sp_send_dbmail for notifications.
     
        .PARAMETER EmailProfile
            The database mail profile used to send emails.
 
        .PARAMETER EmailSubject
            The subject line of the email. The SQL Instance name will be prepended to the subject for easy identification.
 
        .PARAMETER ObjectToAudit
            Specifies which audit the command text is being generated for. The options are Function, StoredProcedure, and Trigger.
 
        .EXAMPLE
            Get-SQLInstanceAuditCommandText -DaysDiff 1 -JobName "Alter_Function_Audit" -OperatorToEmail "Database Administrators" -EmailProfile "MSSQLSERVER Profile" -EmailSubject " - *Alert* Function Modification" -ObjectToAudit Function
             
            Creates the command text to be run by a job to audit CREATE, ALTER, and DROP actions on functions in the SQL Instance.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [System.Int32]$DaysDiff = 1,
        [Parameter(Mandatory=$true)]
        [System.String]$JobName,
        [Parameter(Mandatory=$true)]
        [System.String]$OperatorToEmail,
        [Parameter(Mandatory=$true)]
        [System.String]$EmailProfile,
        [Parameter(Mandatory=$true)]
        [System.String]$EmailSubject,
        [Parameter(Mandatory=$true)]
        [ValidateSet("Function", "StoredProcedure", "Trigger")]
        [System.String]$ObjectToAudit
    )

    Begin {

    }

    Process {
        [System.String]$ObjectTypes = ""
        [System.String]$ObjectName = ""
        [System.String]$Events = "(46, 47, 164)" #ALTER, CREATE, DROP

        switch ($ObjectToAudit) {
            "Function" {
                $ObjectName = "FunctionName"
                $ObjectTypes = "(17985, 17993, 18000, 18002, 18004, 20038, 21318, 21321, 21574)"
            }
            "StoredProcedure" {
                $ObjectName = "StoredProcedureName"
                $ObjectTypes = "(8276, 16724, 21076)"
            }
            "Trigger" {
                $ObjectName = "TriggerName"
                $ObjectTypes = "(8272, 8280, 17232)"
            }
        }

        [System.String]$Command = @"
USE [master];
 
DECLARE @filename varchar(255) = (SELECT TOP 1 path FROM master.sys.traces WHERE is_default = 1 ORDER BY last_event_time DESC);
 
CREATE TABLE #temp
    (
    DatabaseName varchar(255),
    $ObjectName varchar(255),
    ObjectId varchar(255),
    StartTime datetime,
    NTUserName varchar(255),
    LoginName varchar(255),
    HostName varchar(255),
    ApplicationName varchar(255),
    DBUserName varchar(255),
    SPID int,
    EventClass int,
    EventName varchar(255),
    EndTime datetime,
    MethodName varchar(255),
    TransactionID int,
    FileName varchar(255),
    IsSystem tinyint,
    ObjectType int
    )
 
INSERT INTO #temp
SELECT DISTINCT
    gt.DatabaseName,
    gt.ObjectName AS $ObjectName,
    gt.ObjectID,
    gt.StartTime,
    gt.HostName,
    gt.NTUserName,
    gt.LoginName,
    gt.ApplicationName,
    gt.DBUserName,
    gt.SPID,
    gt.EventClass,
    te.name AS EventName,
    gt.EndTime,
    gt.MethodName,
    gt.TransactionID,
    gt.FileName,
    gt.IsSystem,
    gt.ObjectType
FROM [fn_trace_gettable](@filename, DEFAULT) gt
    JOIN sys.trace_events te
    ON gt.EventClass = te.trace_event_id
WHERE
    gt.EventClass in $Events AND
    gt.ObjectType in $ObjectTypes AND
    DATEDIFF(dd, gt.StartTime, GETDATE()) < '$DaysDiff'
ORDER BY gt.StartTime DESC;
 
IF (SELECT count(*) FROM #temp) > 1
BEGIN
DECLARE @Html varchar(max) = '';
DECLARE @HtmlHeaders varchar(max) = '';
DECLARE @HtmlRows varchar(max) = '';
 
--get header, columns name
SELECT @HtmlHeaders = @HtmlHeaders + '<th>' + name + '</th>' FROM tempdb.sys.columns WHERE object_id = object_id('tempdb.dbo.#temp');
 
--convert table to XML PATH, ELEMENTS XSINIL is used to include NULL values
SET @HtmlRows = (SELECT * FROM #temp FOR XML PATH('tr'), ELEMENTS XSINIL)
 
--convert the way ELEMENTS XSINIL display NULL to display word NULL
SET @HtmlRows = REPLACE(@HtmlRows, 'xsi:nil="true"/>', '>NULL</td>');
SET @HtmlRows = REPLACE(@HtmlRows, '<tr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">', '<tr>')
 
--FOR XML PATH will set tags for each column name, <columnName1>abc</columnName1><columnName2>def</columnName2>
--this will replace all the column names with TD (html table data tag)
SELECT @HtmlRows = REPLACE(REPLACE(@HtmlRows, '<' + name + '>', '<td>'), '</' + name + '>', '</td>')
FROM tempdb.sys.columns WHERE object_id = object_id('tempdb.dbo.#temp')
 
SET @Html = '<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>$JobName</title>
    </head>
    <style>
        .table {
            width:100%;
            table-layout:fixed;
            border:1px solid black;
        }
         
        .table td {
            word-break:break-all;
            word-wrap:break-word;
            vertical-align:top;
            text-align:left;
        }
 
        .table th {
            text-align:center;
        }
    </style>
        <body style="width:1200px;margin-left:auto;margin-right:auto;">
            <h1 style="text-align:center;">$JobName</h1>
                <div>
                    <table class="table"><thead>'
                    + '<tr>' + @HtmlHeaders + '</tr>'
                    + '</thead><tbody>'
                    + @HtmlRows
                    + '</tbody></table></div></body></html>';
 
DECLARE @Recipients varchar(max) = (SELECT email_address FROM msdb.dbo.sysoperators WHERE name ='$OperatorToEmail');
DECLARE @Subject varchar(255) = @@ServiceName + '$EmailSubject';
 
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '$EmailProfile',
@body_format = 'HTML',
@body = @Html,
@recipients = @Recipients,
@subject = @Subject
 
END
 
DROP TABLE #temp;
"@

    }

    End {
        Write-Output $Command
    }
}

Function Get-SQLDatabaseDdlTriggerCommandText {
    <#
        .SYNOPSIS
            Gets the command text to be used with creating Database DDL triggers to audit modifications to Functions, Triggers, and Stored Procedures.
 
        .DESCRIPTION
            The Get-SQLDatabaseDdlTriggerCommandText cmdlet creates the T-SQL commands run by a trigger to audit CREATE, ALTER, and DROP commands issued against Functions, Triggers, and Stored Procedures.
 
            The gets the event notification of the action. It collects the information and sends an HTML formatted email to the email addresses in the specified Operator name.
 
        .PARAMETER Subject
            The subject line of the email. The SQL Instance name will be prepended to the subject for easy identification.
 
        .PARAMETER OperatorToEmail
            The name of the Operator whose email addresses will be used with sp_send_dbmail for notifications.
     
        .PARAMETER EmailProfile
            The database mail profile used to send emails.
 
        .EXAMPLE
            Get-SQLDatabaseDdlTriggerCommandText -OperatorToEmail "Database Administrators" -EmailProfile "MSSQLSERVER Profile" -Subject " $DBName - *Alert* SQL Modification"
             
            Creates the command text to be run by a trigger to audit CREATE, ALTER, and DROP actions on functions on a specific SQL database. The database name that the trigger is being created for is also included as part of the subject line.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 5/11/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [System.String]$OperatorToEmail,
        [Parameter(Mandatory=$true)]
        [System.String]$Subject,
        [Parameter(Mandatory=$true)]
        [System.String]$EmailProfile
    )

    Begin {
        <#
            Event Data Object
 
            <EVENT_INSTANCE>
                <EventType>event </EventType>
 
                <PostTime>date-time</PostTime>
                <SPID>spid</SPID>
                <ServerName>name </ServerName>
 
                <LoginName>login </LoginName>
                <UserName>name</UserName>
                <DatabaseName>name</DatabaseName>
 
                <SchemaName>name</SchemaName>
                <ObjectName>name</ObjectName>
                <ObjectType>type</ObjectType>
 
                <TSQLCommand>command</TSQLCommand>
            </EVENT_INSTANCE>
        #>

    }

    Process {
        $Command = @"
SET NOCOUNT ON;
                 
DECLARE @EventData XML = EVENTDATA()
DECLARE @IpAddress varchar(32) = (SELECT client_net_address FROM master.sys.dm_exec_connections WHERE session_id = @@SPID);
         
CREATE TABLE #temp (
    EventDate datetime,
    EventType varchar(255),
    UserName varchar(255),
    EventDDL varchar(max),
    DatabaseName varchar(255),
    SchemaName varchar(255),
    ObjectName varchar(255),
    HostName varchar(255),
    IPAddress varchar(32),
    ProgramName varchar(255)
);
 
INSERT INTO #temp
SELECT
    @EventData.value('(/EVENT_INSTANCE/PostTime)[1]', 'datetime'),
    @EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(255)'),
    @EventData.value('(/EVENT_INSTANCE/LoginName)[1]', 'varchar(255)'),
    @EventData.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'varchar(max)'),
    @EventData.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'varchar(255)'),
    @EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'varchar(255)'),
    @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'varchar(255)'),
    HOST_NAME(),
    @IpAddress,
    PROGRAM_NAME()
 
DECLARE @Html varchar(max) = '';
DECLARE @HtmlHeaders varchar(max) = '';
DECLARE @HtmlRows varchar(max) = '';
 
--get header, columns name
SELECT @HtmlHeaders = @HtmlHeaders + '<th>' + name + '</th>' FROM tempdb.sys.columns WHERE object_id = object_id('tempdb.dbo.#temp');
 
--convert table to XML PATH, ELEMENTS XSINIL is used to include NULL values
SET @HtmlRows = (SELECT * FROM #temp FOR XML PATH('tr'), ELEMENTS XSINIL)
 
--convert the way ELEMENTS XSINIL display NULL to display word NULL
SET @HtmlRows = REPLACE(@HtmlRows, 'xsi:nil="true"/>', '>NULL</td>');
SET @HtmlRows = REPLACE(@HtmlRows, '<tr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">', '<tr>')
 
--FOR XML PATH will set tags for each column name, <columnName1>abc</columnName1><columnName2>def</columnName2>
--this will replace all the column names with TD (html table data tag)
SELECT @HtmlRows = REPLACE(REPLACE(@HtmlRows, '<' + name + '>', '<td>'), '</' + name + '>', '</td>')
FROM tempdb.sys.columns WHERE object_id = object_id('tempdb.dbo.#temp')
 
SET @Html = '<!DOCTYPE html>
                <html>
                    <head>
                        <meta name="viewport" content="width=device-width" />
                        <title>Modified Stored Procedure/Function/Trigger</title>
                    </head>
                    <style>
                        .table {
                            width:100%;
                            table-layout:fixed;
                            border:1px solid black;
                        }
         
                        .table td {
                            word-break:break-all;
                            word-wrap:break-word;
                            vertical-align:top;
                            text-align:left;
                        }
 
                        .table th {
                            text-align:center;
                        }
                    </style>
                        <body style="width:1200px;margin-left:auto;margin-right:auto;">
                            <h1 style="text-align:center;">Modified Stored Procedure/Function/Trigger</h1>
                                <div>
                                    <table class="table"><thead>'
                                    + '<tr>' + @HtmlHeaders + '</tr>'
                                    + '</thead><tbody>'
                                    + @HtmlRows
                                    + '</tbody></table></div></body></html>';
                 
DECLARE @Recipients varchar(max) = (SELECT email_address FROM msdb.dbo.sysoperators WHERE name = '$OperatorToEmail');
DECLARE @Subject varchar(255) = @@ServiceName + '$Subject'
 
EXEC msdb.dbo.sp_send_dbmail
    @profile_name = '$EmailProfile',
    @body_format = 'HTML',
    @body = @Html,
    @recipients = @Recipients,
    @subject = @Subject
                 
 
DROP TABLE #temp;
"@

    }

    End {
        Write-Output $Command
    }
}

Function New-SQLInstanceDatabaseDirectoryAccessRuleSet {
    <#
        .SYNOPSIS
            Creates the access rule set for the SQL Database Files and Folders.
 
        .DESCRIPTION
            The New-DatabaseDirectoryRuleSet cmdlet creates the access rules.
 
        .EXAMPLE
            New-DatabaseDirectoryRuleSet
 
            Creates the access rules.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Security.AccessControl.FileSystemAccessRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LASTEDIT: 4/16/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [System.Security.Principal.SecurityIdentifier]$SqlServiceAccountSid,
        [Parameter(Mandatory=$true)]
        [System.Security.Principal.SecurityIdentifier]$AdministratorsSid,
        [Parameter()]
        [System.Security.Principal.SecurityIdentifier]$SqlAgentSid = $null,
        [Parameter()]
        [switch]$IncludeLocalAdministrators
    )

    Begin {}

    Process
    {
        $SqlServiceAce = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($SqlServiceAccountSid,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

        $AdministratorsAce = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($AdministratorsSid,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

        [System.Security.AccessControl.FileSystemAccessRule[]] $Rules = @($AdministratorsAce,$SqlServiceAce)

        if ($SqlAgentSid -ne $null) {
            $SqlAgentAce = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($SqlAgentSid,
                @([System.Security.AccessControl.FileSystemRights]::ReadAndExecute, [System.Security.AccessControl.FileSystemRights]::Write),
                @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
                [System.Security.AccessControl.PropagationFlags]::None,
                [System.Security.AccessControl.AccessControlType]::Allow
            )

            $Rules += $SqlAgentAce
        }

        if ($IncludeLocalAdministrators) {
            $BuiltinAdministrators = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null)

            $LocalAdministratorsAce = New-Object System.Security.AccessControl.FileSystemAccessRule($BuiltinAdministrators,
                [System.Security.AccessControl.FileSystemRights]::FullControl,
                @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
                [System.Security.AccessControl.PropagationFlags]::None,
                [System.Security.AccessControl.AccessControlType]::Allow)  

            $Rules += $LocalAdministratorsAce  
        }
    }

    End
    {
        Write-Output $Rules
    }
}

Function New-SQLInstanceAuditLogAccessRuleSet {
    <#
        .SYNOPSIS
            Creates the access rule set for the SQL Audit Logs.
 
        .DESCRIPTION
            The New-AuditLogRuleSet cmdlet creates the access rules.
 
        .EXAMPLE
            New-AuditLogRuleSet
 
            Creates the access rules.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Security.AccessControl.FileSystemAccessRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LASTEDIT: 4/16/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [System.Security.Principal.SecurityIdentifier]$SqlServiceAccountSid,
        [Parameter(Mandatory=$true)]
        [System.Security.Principal.SecurityIdentifier]$AuditAdministratorSid,
        [Parameter(Mandatory=$true)]
        [System.Security.Principal.SecurityIdentifier]$AuditorsSid,
        [Parameter()]
        [System.Security.Principal.SecurityIdentifier]$SqlAgentSid = $null

    )

    Begin {}

    Process
    {
        <#
            Administrator(read)
 
            Users (none)
 
            Audit Administrator(Full Control)
 
            Auditors group (Read)
 
            SQL Server Service SID OR Service Account (Full Control)
 
            SQL Server SQL Agent Service SID OR Service Account, if SQL Server Agent is in use. (Read, Execute, Write)
        #>


        $BuiltinAdministrators = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null)

        $AdministratorsAce = New-Object System.Security.AccessControl.FileSystemAccessRule($BuiltinAdministrators,
            [System.Security.AccessControl.FileSystemRights]::Read,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )


        $AuditAdminAce = New-Object System.Security.AccessControl.FileSystemAccessRule($AuditAdministratorSid,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

        $AuditorsAce = New-Object System.Security.AccessControl.FileSystemAccessRule($AuditorsSid,
            [System.Security.AccessControl.FileSystemRights]::Read,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

        $SqlServiceAce = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($SqlServiceAccountSid,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

        [System.Security.AccessControl.FileSystemAccessRule[]] $Rules = @($AdministratorsAce,$AuditAdminAce,$AuditorsAce, $SqlServiceAce)

        if ($SqlAgentSid -ne $null) {
            $SqlAgentAce = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($SqlAgentSid,
                @([System.Security.AccessControl.FileSystemRights]::ReadAndExecute, [System.Security.AccessControl.FileSystemRights]::Write),
                @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
                [System.Security.AccessControl.PropagationFlags]::None,
                [System.Security.AccessControl.AccessControlType]::Allow
            )

            $Rules += $SqlAgentAce
        }
    }

    End
    {
        Write-Output $Rules
    }
}

Function New-SQLInstanceInstallationDirectoryAccessRuleSet {
    <#
        .SYNOPSIS
            Creates the access rule set for the SQL installation directory.
 
        .DESCRIPTION
            The New-InstallationDirectoryRuleSet cmdlet creates the access rules.
 
        .EXAMPLE
            New-InstallationDirectoryRuleSet
 
            Creates the access rules.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.DirectoryServices.FileSystemAccessRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LASTEDIT: 4/16/2016
    #>


    [CmdletBinding()]
    Param()

    Begin {

    }

    Process {
        $BuiltinAdministrators = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null)

        $AdministratorAce = New-Object System.Security.AccessControl.FileSystemAccessRule($BuiltinAdministrators,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )

        $TrustedInstallerObj = New-Object -TypeName System.Security.Principal.NTAccount("NT Service", "TrustedInstaller")
        $TrustedInstaller = ($TrustedInstallerObj.Translate([System.Security.Principal.SecurityIdentifier]))

        $TrustedInstallerAce = New-Object System.Security.AccessControl.FileSystemAccessRule($TrustedInstaller,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )

        $System = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null)

        $SystemAce = New-Object System.Security.AccessControl.FileSystemAccessRule($System,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )

        $Users = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinUsersSid, $null)

        $UsersAce = New-Object System.Security.AccessControl.FileSystemAccessRule($Users,
            @([System.Security.AccessControl.FileSystemRights]::ListDirectory, [System.Security.AccessControl.FileSystemRights]::ReadAndExecute),
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )

        $CreatorOwner = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::CreatorOwnerSid, $null)

        $CreatorOwnerAce = New-Object System.Security.AccessControl.FileSystemAccessRule($CreatorOwner,
            @([System.Security.AccessControl.FileSystemRights]::FullControl),
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::InheritOnly,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )

        $AllAppPackages = New-Object -TypeName System.Security.Principal.SecurityIdentifier("S-1-15-2-1")

        $AllAppPackagesAce = New-Object System.Security.AccessControl.FileSystemAccessRule($AllAppPackages,
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow       
        )

        [System.Security.AccessControl.FileSystemAccessRule[]] $Rules = @($AdministratorsAce,$TrustedInstallerAce,$SystemAce,$UsersAce,$CreatorOwnerAce,$AllAppPackagesAce)
    }

    End {
        Write-Output $Rules
    }
}

Function New-SQLInstanceInstallationDirectoryAuditRuleSet {
    <#
        .SYNOPSIS
            Creates the audit rule set for the SQL installation directory.
 
        .DESCRIPTION
            The New-InstallationDirectoryAuditRuleSet cmdlet builds the required audit rule for auditing the SQL server installation directory.
 
        .EXAMPLE
            New-InstallationDirectoryAuditRuleSet
 
            Creates the audit rules.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Security.AccessControl.FileSystemAuditRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATED: 4/16/2016
    #>


    [CmdletBinding()]
    Param()

    Begin
    {
        $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null)
    }
    
    Process
    {
        $EveryoneFail = New-Object System.Security.AccessControl.FileSystemAuditRule($Everyone,
            [System.Security.AccessControl.FileSystemRights]::Modify, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AuditFlags]::Failure)

        $EveryoneSuccess = New-Object System.Security.AccessControl.FileSystemAuditRule($Everyone,
            [System.Security.AccessControl.FileSystemRights]::Modify, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AuditFlags]::Success)

        [System.Security.AccessControl.FileSystemAuditRule[]]$Rules = @($EveryoneFail, $EveryoneSuccess)
    }

    End
    {
        Write-Output $Rules
    }
}

Function Get-SQLAuditObjectTypes {
    [CmdletBinding()]
    Param()

    Begin {}

    Process {}

    End {
        Write-Output $script:AuditObjectTypes
    }
}

Function Import-SqlModule {
    <#
        .SYNOPSIS
            Imports the SQLPS module and snapins.
 
        .DESCRIPTION
            The cmdlet checks for the SQLPS, Microsoft.SqlServer.Management.PSSnapins, and Microsoft.SqlServer.Management.PSProvider modules and loads them. It optionally adds the SQL Server Management Objects .NET assembly.
 
            At the end of the cmdlet, the PSDrive is set to SQLSERVER:\.
 
        .PARAMETER Version
            The version of SQL being configured, select from either 10 (SQL 2005), 11 (SQL 2008), 12 (SQL 2014), or 13 (SQL 2016). This defaults to 12. It is used to locate the correct modules in the filesystem.
 
        .INPUTS
            System.Int
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Import-SqlModule
 
            Imports the SQL modules.
 
        .EXAMPLE
            Import-SqlModule -LoadSMO
 
            Imports the SQL modules and loads the SMO .NET assembly.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/18/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true)]
        [ValidateSet(10,11,12,13)]
        [int]$Version = 12,
        [Parameter(Position=1)]
        [switch]$LoadSMO
    )

    Begin {

    }

    Process {
        Write-Verbose -Message "Loading SQLPS PowerShell Modules."

        if (!(Get-Module -Name SQLPS)) {

            Import-Module -Name "${env:ProgramFiles(x86)}\Microsoft SQL Server\$($Version)0\Tools\PowerShell\Modules\SQLPS\SQLPS.PS1" -ErrorAction Stop -DisableNameChecking
            Write-Verbose -Message "Successfully loaded SQLPS Module."
            Write-Debug -Message "Successfully loaded SQLPS Module."
        }
        else {
            Write-Debug "SQLPS module already loaded"
        }
        
        if (!(Get-Module -Name Microsoft.SqlServer.Management.PSSnapins)) {                                                                                                                                                     
            Import-Module -Name "${env:ProgramFiles(x86)}\Microsoft SQL Server\$($Version)0\Tools\PowerShell\Modules\SQLPS\Microsoft.SqlServer.Management.PSSnapins.dll" -ErrorAction Stop -DisableNameChecking
            Write-Verbose -Message "Successfully added SQL PS Snapin module."
            Write-Debug -Message "Successfully added SQL PS Snapin module."
        }
        else {
            Write-Debug -Message "Microsoft.SqlServer.Management.PSSnapins module already loaded."
        }

        if(!(Get-Module -Name Microsoft.SqlServer.Management.PSProvider)) {
            Import-Module -Name "${env:ProgramFiles(x86)}\Microsoft SQL Server\$($Version)0\Tools\PowerShell\Modules\SQLPS\Microsoft.SqlServer.Management.PSProvider.dll" -ErrorAction Stop -DisableNameChecking
            Write-Verbose -Message "Successfully added SQL PS Provider module."
            Write-Debug -Message "Successfully added SQL PS Provider module."
        }
        else {
            Write-Debug -Message "Microsoft.SqlServer.Management.PSProvider module already loaded."
        }

        if ($LoadSMO) {
            [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Smo') | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfoExtended") | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
            Write-Debug -Message 'SQL Server Management Objects .NET assembly successfully loaded'
        }
    }

    End {
        Push-Location "SQLSERVER:"
    }
}

Function Get-AccountSid {
    <#
        .SYNOPSIS
            Gets the SID of a given username.
 
        .DESCRIPTION
            The cmdlet gets the SID of a username, which could a service account, local account, or domain account.
 
        .PARAMETER UserName
            The name of the user or service account to get the SID of.
 
        .PARAMETER ComputerName
            If the account is local to another machine, such as an NT SERVICE account or a true local account, specify the computer name the account is on.
 
        .PARAMETER SqlServer
            An existing connection to a SQL Instance using a Microsoft.SqlServer.Management.Smo.Server object. Use this enumerate the SID from a SQL Login.
 
        .PARAMETER Credential
            The credentials used to connect to the remote machine.
             
        .INPUTS
            None
 
        .OUTPUTS
            System.Security.Principal.SecurityIdentifier
 
        .EXAMPLE
            Get-AccountSid -UserName "Administrator"
 
            Gets the SID for the Administrator account.
 
        .EXAMPLE
            Get-AccountSid -UserName "NT AUTHORITY\Authenticated Users"
 
            Gets the SID for the Authenticated Users group.
 
        .EXAMPLE
            Get-AccountSid -UserName "NT AUTHORITY\System"
 
            Gets the SID for the SYSTEM account. The user name could also just be "System".
 
        .EXAMPLE
            Get-AccountSid -UserName "NT SERVICE\MSSQLSERVER" -ComputerName SqlServer
 
            Gets the SID for the virtual MSSQLSERVER service principal.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/20/2016
    #>

    [CmdletBinding(DefaultParameterSetName="Computer")]
    Param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [string]$UserName,
        [Parameter(Position=1, ParameterSetName="Computer")]
        [string]$ComputerName = [System.String]::Empty,
        [Parameter(Position=1, ParameterSetName="Sql", Mandatory=$true)]
        $SqlServer,
        [Parameter(ParameterSetName="Computer")] 
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty  
    )

    Begin {
        Write-Verbose "Getting SID for $UserName."
    }

    Process{
        if ($PSCmdlet.ParameterSetName -eq "Sql") {
            if ($SqlServer -ne $null -and $SqlServer.GetType() -ne [Microsoft.SqlServer.Management.Smo.Server]) {
                throw "SqlServer parameter must be an existing Microsoft.SqlServer.Management.Smo.Server object."
            }
            $Offset = 0
            $UserSid = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Byte[]]($SqlServer.Logins | Where-Object {$_.Name -eq $UserName} | Select-Object -First 1 | Select-Object -ExpandProperty Sid), $Offset)
        }
        else {
            if (![System.String]::IsNullOrEmpty($UserName)) {
                if ($UserName.IndexOf("\") -ne -1) {
                    $Parts = $UserName.Split("\")
                    $Domain = $Parts[0]
                    $Name = $Parts[1]            
                }
                elseif ($UserName.IndexOf("@") -ne -1) {
                    $Parts = $UserName.Split("@")
                    $Domain = $Parts[1]
                    $Name = $Parts[0]
                }
                else {
                    try {
                        $Domain = (Get-ADDomain -Current LocalComputer -ErrorAction Stop).Name
                    }
                    catch [Exception] {
                        $Domain = $ComputerName
                    }

                    $Name = $UserName
                }

                if ($ComputerName -iin $script:LocalNames) {
                    $User = New-Object -TypeName System.Security.Principal.NTAccount($Domain, $Name)
                    $UserSid = $User.Translate([System.Security.Principal.SecurityIdentifier])
                }
                else {
                    $Session = New-PSSession -ComputerName $ComputerName -Credential $Credential
                
                     $UserSid = Invoke-Command -Session $Session -ScriptBlock { 
                        $User = New-Object -TypeName System.Security.Principal.NTAccount($args[0], $args[1])
                        Write-Output $User.Translate([System.Security.Principal.SecurityIdentifier])
                    } -ArgumentList @($Domain, $Name)

                    Remove-PSSession -Session $Session
                }
            }
            else {
                $UserSid = $null
            }
        }
    }

    End {
        Write-Output $UserSid
    }
}

Function Set-FilePermissions {
    <#
        .SYNOPSIS
            Sets permissions on a file or directory.
 
        .DESCRIPTION
            Will set permissions on file or directory with the provided rule set.
 
        .PARAMETER Path
            The path to the file to set permissions on.
 
        .PARAMETER Rules
            An array of File Access Rules to apply to the path.
 
        .PARAMETER Replace
            Indictates if all permissions on the path should be replaced with these. Otherwise the specified access rules will just be added to the target.
 
        .PARAMETER ForceInheritance
            Indicates if all permissions of child items should have their permissions replaced with these if the target is a directory.
 
        .EXAMPLE
            Set-Permissions -Path "c:\test.txt" -Rules $Rules
 
            Creates the rule set on the test.txt file.
 
        .INPUTS
            System.String, System.Security.AccessControl.FileSystemAccessRule[], System.Management.Automation.SwitchParameter, System.Management.Automation.SwitchParameter
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param 
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [string]$Path,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [System.Security.AccessControl.FileSystemAccessRule[]]$Rules,
        [Parameter(Position=2,ValueFromPipeLineByPropertyName=$true)]
        [switch]$Replace = $false,
        [Parameter(Position=3,ValueFromPipeLineByPropertyName=$true)]
        [switch]$ForceInheritance = $false
    )

    Begin 
    {
        Write-Verbose -Message "Setting permissions on $Path."
        Push-Location -Path $env:SystemDrive
    }

    Process
    {
        try
        {
            $Acl = Get-Acl -Path $Path

            if ($Acl -ne $null)
            {
                #Should the permissions be replaced
                if($Replace)
                {
                    $OldAcls = $Acl.Access

                    foreach ($Rule in $OldAcls)
                    {
                        $Acl.RemoveAccessRule($Rule) | Out-Null
                    }
                }
                #Only remove the permissions for principals we're updating
                else
                {
                    $OldAcls = $Acl.Access | Where-Object {$Rules.IdentityReference -eq  $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])}

                    foreach ($Rule in $OldAcls)
                    {
                        $Acl.RemoveAccessRule($Rule) | Out-Null
                    }
                }

                foreach ($Rule in $Rules)
                {
                    $Acl.AddAccessRule($Rule) | Out-Null
                }

                Set-Acl -Path $Path -AclObject $Acl

                #If child permissions should be forced to inherit
                if ($ForceInheritance)
                {
                    Get-ChildItem -Path $Path -Recurse -Force | ForEach-Object {

                        $ChildAcl = Get-Acl -Path $_.FullName 
                        $ChildPath = $_.FullName

                        Write-Debug -Message "Forcing inheritance on $ChildPath."

                        foreach ($ChildRule in $ChildAcl.Access)
                        {
                            try
                            {
                                $ChildAcl.RemoveAccessRule($ChildRule) | Out-Null
                            }
                            catch [Exception]
                            {
                                Write-Warning "Error removing ACL from $ChildPath`: $($_.ToString())"
                            }
                        }

                        $ChildAcl.SetAccessRuleProtection($false,$false)

                        Set-Acl -Path $_.FullName -AclObject $ChildAcl | Out-Null
                    }
                }
            }
            else
            {
                Write-Warning "Could not retrieve the ACL for $Path"
            }
        }
        catch [System.Exception]
        {
            Write-Warning $_.Exception.Message
        }
    }
    
    End {
        Pop-Location 
    }
}

Function Reset-InheritedPermissions {
    <#
        .SYNOPSIS
            Resets a folder or a file's inherited permissions.
 
        .DESCRIPTION
            Will force inherited permissions at the path and optionally all of its children.
 
        .PARAMETER Path
            The path to the file to reset permissions on.
 
        .PARAMETER ResetChildPermissions
 
        .EXAMPLE
            Reset-InheritedPermissions -Path "c:\Program Files\Microsoft SQL Server"
 
            Forces inherited permissions on the Microsoft SQL Server directory and all of its children.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/20/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [string]$Path,
        [Parameter(Position=1)]
        [switch]$ResetChildPermissions
    )

    Begin {
        Write-Verbose -Message "Setting inherited permissions on $Path."
        Push-Location -Path c:\
    }

    Process {
        try
        {
            $Acl = Get-Acl -Path $Path

            if ($Acl -ne $null)
            {
                $OldAcls = $Acl.Access

                foreach ($Rule in $OldAcls)
                {
                    $Acl.RemoveAccessRule($Rule) | Out-Null
                }
                
                $Acl.SetAccessRuleProtection($false,$false)            
            
                Set-Acl -Path $Path -AclObject $Acl

                if ($ResetChildPermissions) {

                    Get-ChildItem -Path $Path -Recurse -Force | ForEach-Object {

                        Write-Debug -Message "Setting inherited permissions on $ChildPath."

                        $ChildAcl = Get-Acl -Path $_.FullName 
                        $ChildPath = $_.FullName

                        foreach ($ChildRule in $ChildAcl.Access)
                        {
                            try
                            {
                                $ChildAcl.RemoveAccessRule($ChildRule) | Out-Null
                            }
                            catch [Exception]
                            {
                                Write-Warning "Error removing ACL from $ChildPath : $($_.Exception.Message)"
                            }
                        }

                        $ChildAcl.SetAccessRuleProtection($false,$false)

                        Set-Acl -Path $_.FullName -AclObject $ChildAcl | Out-Null
                    }
                }
            }
            else
            {
                Write-Warning "Could not retrieve the ACL for $Path"
            }
        }
        catch [System.Exception]
        {
            Write-Warning $_.Exception.Message
        }
    }

    End {
        Pop-Location
    }
}

Function Set-Auditing {
    <#
        .SYNOPSIS
            Sets auditing on file system object.
 
        .DESCRIPTION
            The Set-Auditing cmdlet applies an audit rule set to a file system object.
 
        .PARAMETER Path
            The path to set auditing on.
 
        .PARAMETER AuditRules
            The array of FileSystemAuditRule.
 
        .PARAMETER ForceInheritance
            Forces inherited permissions on child objects.
 
        .EXAMPLE
            Set-Auditing -Path "c:\windows" -AuditRules $Rules
 
            Implements the audit rules.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LASTEDIT: 4/16/2016
    #>


    [CmdletBinding()]
    Param 
    (
        [Parameter(Position=0,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [string]$Path,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [System.Security.AccessControl.FileSystemAuditRule[]]$AuditRules,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [switch]$ForceInheritance
    )

    Begin
    {
        Push-Location $env:SystemDrive
    }

    Process
    {
        try
        {
            $Acl = Get-Acl -Path $Path -Audit

            if ($Acl -ne $null)
            {
                foreach ($Rule in $Rules)
                {
                    $Acl.AddAuditRule($Rule)
                }

                Set-Acl -Path $Path -AclObject $Acl

                #Enables inheritance on child objects
                if ($ForceInheritance) {
                    Get-ChildItem -Path $Path -Recurse -Force | ForEach-Object {

                        $ChildAcl = Get-Acl -Path $_.FullName -Audit
                        $ChildPath = $_.FullName

                        Write-Debug -Message "Forcing inheritance on $ChildPath"
                    
                        #Allows inheritance to change rules
                        $ChildAcl.SetAuditRuleProtection($false,$false)

                        #Because audit rules weren't explicitly removed, they are still present in addition to the inherited ones.
                        Set-Acl -Path $_.FullName -AclObject $ChildAcl | Out-Null
                    }
                }
            }
            else
            {
                Write-Warning "Could not retrieve the ACL for $Path."
            }
        }
        catch [System.Exception]
        {
            Write-Warning $_.ToString()
        }
    }

    End {
        Pop-Location
    }
}

Function Where-NotMatchIn {
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true)]
        [Object[]]$Input,
        [Parameter(Position=1)]
        [string[]]$Matches,
        [Parameter()]
        [string]$Property = [System.String]::Empty
    )

    Begin {
        $ReturnArray = @()
    }

    Process {

        foreach($Item in $Input) {
            $Match = $false

            if ($Property -eq [System.String]::Empty) {
                $Value = $Item
            }
            else {
                $Value = $Item.$Property
            }

            foreach ($Matcher in $Matches) {
                if ($Value -like $Matcher) {
                    $Match = $true
                    break
                }
            }

            if (!$Match) {
                $ReturnArray += $Item
            }
        }
    }

    End {
        Write-Output $ReturnArray
    }
}

#endregion

$script:AuditObjectTypes = @(
    [PSCustomObject]@{Id = 8259; Name = "Check Constraint"},
    [PSCustomObject]@{Id = 8260; Name = "Default (constraint or standalone)"},
    [PSCustomObject]@{Id = 8262; Name = "Foreign-key Constraint" },
    [PSCustomObject]@{Id = 8272; Name = "Stored Procedure" },
    [PSCustomObject]@{Id = 8274; Name = "Rule" },
    [PSCustomObject]@{Id = 8275; Name = "System Table"},
    [PSCustomObject]@{Id = 8276; Name = "Trigger on Server"},
    [PSCustomObject]@{Id = 8277; Name = "(User-defined) Table"},
    [PSCustomObject]@{Id = 8278; Name = "View"},
    [PSCustomObject]@{Id = 8280; Name = "Extended Stored Procedure"},
    [PSCustomObject]@{Id = 16724; Name = "CLR Trigger"},
    [PSCustomObject]@{Id = 16964; Name = "Database"},
    [PSCustomObject]@{Id = 16975; Name = "Object"},
    [PSCustomObject]@{Id = 17222; Name = "FullText Catalog"},
    [PSCustomObject]@{Id = 17232; Name = "CLR Stored Procedure"},
    [PSCustomObject]@{Id = 17235; Name = "Schema"},
    [PSCustomObject]@{Id = 17475; Name = "Credential"},
    [PSCustomObject]@{Id = 17491; Name = "DDL Event"},
    [PSCustomObject]@{Id = 17741; Name = "Management Event"},
    [PSCustomObject]@{Id = 17747; Name = "Security Event"},
    [PSCustomObject]@{Id = 17749; Name = "User Event"},
    [PSCustomObject]@{Id = 17985; Name= "CLR Aggregate Function"},
    [PSCustomObject]@{Id = 17993; Name = "Inline Table-valued SQL Function"},
    [PSCustomObject]@{Id = 18000; Name = "Partition Function"},
    [PSCustomObject]@{Id = 18002; Name = "Replication Filter Procedure"},
    [PSCustomObject]@{Id = 18004; Name = "Table-valued SQL Function"},
    [PSCustomObject]@{Id = 18259; Name = "Server Role"},
    [PSCustomObject]@{Id = 18263; Name = "Microsoft Windows Group"},
    [PSCustomObject]@{Id = 19265; Name = "Asymmetric Key"},
    [PSCustomObject]@{Id = 19277; Name = "Master Key"},
    [PSCustomObject]@{Id = 19280; Name = "Primary Key"},
    [PSCustomObject]@{Id = 19283; Name = "ObfusKey"},
    [PSCustomObject]@{Id = 19521; Name = "Asymmetric Key Login"},
    [PSCustomObject]@{Id = 19523; Name = "Certificate Login"},
    [PSCustomObject]@{Id = 19538; Name = "Role"},
    [PSCustomObject]@{Id = 19539; Name = "SQL Login"},
    [PSCustomObject]@{Id = 19543; Name = "Windows Login"},
    [PSCustomObject]@{Id = 20034; Name = "Remote Service Binding"},
    [PSCustomObject]@{Id = 20036; Name = "Event Notification on Database"},
    [PSCustomObject]@{Id = 20037; Name = "Event Notification"},
    [PSCustomObject]@{Id = 20038; Name = "Scalar SQL Function"},
    [PSCustomObject]@{Id = 20047; Name = "Event Notification on Object"},
    [PSCustomObject]@{Id = 20051; Name = "Synonym"},
    [PSCustomObject]@{Id = 20307; Name = "Sequence"},
    [PSCustomObject]@{Id = 20549; Name = "End Point"},
    [PSCustomObject]@{Id = 20801; Name = "Adhoc Queries which may be cached"},
    [PSCustomObject]@{Id = 20816; Name = "Prepared Queries which may be cached"},
    [PSCustomObject]@{Id = 20819; Name = "Service Broker Service Queue"},
    [PSCustomObject]@{Id = 20821; Name = "Unique Constraint"},
    [PSCustomObject]@{Id = 21057; Name = "Application Role"},
    [PSCustomObject]@{Id = 21059; Name = "Certificate"},
    [PSCustomObject]@{Id = 21075; Name = "Server"},
    [PSCustomObject]@{Id = 21076; Name = "Transact-SQL Trigger"},
    [PSCustomObject]@{Id = 21313; Name = "Assembly"},
    [PSCustomObject]@{Id = 21318; Name = "CLR Scalar Function"},
    [PSCustomObject]@{Id = 21321; Name = "Inline scalar SQL Function"},
    [PSCustomObject]@{Id = 21328; Name = "Partition Scheme"},
    [PSCustomObject]@{Id = 21333; Name = "User"},
    [PSCustomObject]@{Id = 21571; Name = "Service Broker Service Contract"},
    [PSCustomObject]@{Id = 21572; Name = "Trigger on Database"},
    [PSCustomObject]@{Id = 21574; Name = "CLR Table-valued Function"},
    [PSCustomObject]@{Id = 21577; Name = "Internal Table (For example, XML Node Table, Queue Table.)"},
    [PSCustomObject]@{Id = 21581; Name = "Service Broker Message Type"},
    [PSCustomObject]@{Id = 21586; Name = "Service Broker Route"},
    [PSCustomObject]@{Id = 21587; Name = "Statistics"}
    [PSCustomObject]@{Id = 21825; Name = "User"},
    [PSCustomObject]@{Id = 21827; Name = "User"},
    [PSCustomObject]@{Id = 21831; Name = "User"},
    [PSCustomObject]@{Id = 21843; Name = "User"},
    [PSCustomObject]@{Id = 21847; Name = "User"},
    [PSCustomObject]@{Id = 22099; Name = "Service Broker Service"},
    [PSCustomObject]@{Id = 22601; Name = "Index"},
    [PSCustomObject]@{Id = 22604; Name = "Certificate Login"},
    [PSCustomObject]@{Id = 22611; Name = "XMLSchema"},
    [PSCustomObject]@{Id = 22868; Name = "Type"}
)