## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx

#region Function Invoke-SQLDBQuery
Function Invoke-SQLDBQuery
    {
        <#
            .SYNOPSIS
            This function is able to execute one or more queries against an the specified Microsoft SQL database using either Windows or SQL authentication.
          
            .DESCRIPTION
            The database connection will be opened, each database query will be executed, the results will be stored, and the database connection will be closed.
           
            .PARAMETER Server

            .PARAMETER Instance
            
            .PARAMETER Port

            .PARAMETER UserID

            .PARAMETER Password

            .PARAMETER Database

            .PARAMETER DBQueryList
    
            .EXAMPLE
            $DBQueryResults = Invoke-SQLDBQuery -Server 'MyDBServer.mydomain.com' -UserID 'SQLAuthUser' -Password 'SQLAuthUserPassword' -Database 'YourDatabaseName' -DBQueryList @('Select * From YourTableName') -Verbose

            Write-Output -InputObject ($DBQueryResults)
            
            .EXAMPLE
            $DBQueryList = [System.Collections.Generic.List[String]]@()
              $DBQueryList.Add(@"
Select Distinct
    dbo.v_Add_Remove_Programs.DisplayName0 As 'DisplayName'
From
    dbo.v_Add_Remove_Programs
Order By
    dbo.v_Add_Remove_Programs.DisplayName0
"@)

            $DBQueryResults = Invoke-SQLDBQuery -Server 'MyDBServer.mydomain.com' -Database 'YourDatabaseName' -DBQueryList ($DBQueryList) -Verbose

            .EXAMPLE
            $DBQueryResults = Invoke-SQLDBQuery -Server 'MyDBServer.mydomain.com' -Instance 'MyDBInstance' -Database 'YourDatabaseName' -DBQueryList @('Select * From YourTableName')

            Write-Output -InputObject ($DBQueryResults)

            .EXAMPLE
            $DBQueryResults = Invoke-SQLDBQuery -Server 'MyDBServer.mydomain.com' -Port '4530' -Database 'YourDatabaseName' -DBQueryList @('Select * From YourTableName1', 'Select * From YourTableName2')

            Write-Output -InputObject ($DBQueryResults)
                    
            .LINK
            https://www.SQLshack.com/connecting-powershell-to-SQL-server/

            .LINK
            https://stackoverflow.com/questions/64681003/fetch-a-whole-row-from-SQL-server-with-powershell
        #>
        
        [CmdletBinding(ConfirmImpact = 'Low', HelpURI = '', DefaultParameterSetName = '_AllParameterSets', SupportsShouldProcess = $True, PositionalBinding = $True)]       
          Param
            (        	     
                [Parameter(Mandatory=$True)]
                [ValidateNotNullOrEmpty()]
                [String]$Server,

                [Parameter(Mandatory=$False)]
                [ValidateNotNullOrEmpty()]
                [String]$Instance,

                [Parameter(Mandatory=$False)]
                [ValidateNotNullOrEmpty()]
                [String]$Port,
       
                [Parameter(Mandatory=$False)]
                [ValidateNotNullOrEmpty()]
                [String]$UserID,

                [Parameter(Mandatory=$False)]
                [ValidateNotNullOrEmpty()]
                [String]$Password,

                [Parameter(Mandatory=$False)]
                [ValidateNotNullOrEmpty()]
                [String]$Database,
        
                [Parameter(Mandatory=$True)]
                [ValidateNotNullOrEmpty()]
                [String[]]$DBQueryList,

                [Parameter(Mandatory=$False, ParameterSetName = 'Export')]
                [Switch]$ExportJSON,

                [Parameter(Mandatory=$False, ParameterSetName = 'Export')]
                [System.IO.DirectoryInfo]$ExportDirectory,

                [Parameter(Mandatory=$False)]
                [Switch]$ContinueOnError  
            )
                    
        Begin
          {
              [ScriptBlock]$ErrorHandlingDefinition = {
                                                          If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message -Join "`r`n`r`n")"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
          
                                                          [String]$ErrorMessage = "[Error Message: $($ExceptionMessage)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]"

                                                          If ($ContinueOnError.IsPresent -eq $True)
                                                            {
                                                                Write-Warning -Message ($ErrorMessage)
                                                            }
                                                          ElseIf ($ContinueOnError.IsPresent -eq $False)
                                                            {
                                                                Throw ($ErrorMessage)
                                                            }
                                                      }
              
              Try
                {
                    $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF'  ###03/23/2022 11:12:48.347###
                    [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)}
                    $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt'  ###Monday, January 01, 2019 @ 10:15:34.000 AM###
                    [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)}
                    $DateFileFormat = 'yyyyMMdd'  ###20190403###
                    [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)}
                    $DateTimeFileFormat = 'yyyyMMdd_HHmmss'  ###20190403_115354###
                    [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)}
                    $TextInfo = (Get-Culture).TextInfo
                    
                    #Determine the date and time we executed the function
                      $FunctionStartTime = (Get-Date)
                    
                    [String]$CmdletName = $MyInvocation.MyCommand.Name 
                    
                    $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($CmdletName)`' is beginning. Please Wait..."
                    Write-Verbose -Message $LogMessage
              
                    #Define Default Action Preferences
                      $ErrorActionPreference = 'Stop'
                      
                    $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The following parameters and values were provided to the `'$($CmdletName)`' function." 
                    Write-Verbose -Message $LogMessage

                    $FunctionProperties = Get-Command -Name $CmdletName
                    
                    $FunctionParameters = $FunctionProperties.Parameters.Keys | Where-Object {($_ -inotmatch '(^Password$)|(^UserID$)|(^DBQueryList$)')}
              
                    ForEach ($Parameter In $FunctionParameters)
                      {
                          If (!([String]::IsNullOrEmpty($Parameter)))
                            {
                                $ParameterProperties = Get-Variable -Name $Parameter -ErrorAction SilentlyContinue
                                $ParameterValueCount = $ParameterProperties.Value | Measure-Object | Select-Object -ExpandProperty Count
                          
                                If ($ParameterValueCount -gt 1)
                                  {
                                      $ParameterValueStringFormat = ($ParameterProperties.Value | ForEach-Object {"`"$($_)`""}) -Join ", "
                                      $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($ParameterProperties.Name):`r`n`r`n$($ParameterValueStringFormat)"
                                  }
                                Else
                                  {
                                      $ParameterValueStringFormat = ($ParameterProperties.Value | ForEach-Object {"`"$($_)`""}) -Join ', '
                                      $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($ParameterProperties.Name): $($ParameterValueStringFormat)"
                                  }
                           
                                If (!([String]::IsNullOrEmpty($ParameterProperties.Name)))
                                  {
                                      Write-Verbose -Message $LogMessage
                                  }
                            }
                      }

                    $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($CmdletName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))"
                    Write-Verbose -Message $LogMessage
                                        
                    #Create an array list that will contain the functions output
                      $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'

                    #Define Variable(s)
                      $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
                        $LoggingDetails.Add('LogMessage', $Null)
                        $LoggingDetails.Add('WarningMessage', $Null)
                        $LoggingDetails.Add('ErrorMessage', $Null)

                    #Create a database connection string builder
                      $DBConnectionStringBuilder = [System.Data.SQLClient.SQLConnectionStringBuilder]::New()

                    #Create a database data source builder
                      $DataSourceBuilder = [System.Text.StringBuilder]::New()

                    #Dynamically build the connection string based on parameter values
                      Switch ($True)
                        {                
                            {([String]::IsNullOrEmpty($Server) -eq $False) -and ([String]::IsNullOrWhiteSpace($Server) -eq $False)}
                              {
                                  $Null = $DataSourceBuilder.Append($Server)
                              }

                            {([String]::IsNullOrEmpty($Instance) -eq $False) -and ([String]::IsNullOrWhiteSpace($Instance) -eq $False)}
                              {
                                  $Null = $DataSourceBuilder.Append("\$($Instance)")
                              }

                            {([String]::IsNullOrEmpty($Port) -eq $False) -and ([String]::IsNullOrWhiteSpace($Port) -eq $False)}
                              {
                                  $Null = $DataSourceBuilder.Append(",$($Port)")
                              }

                            {([String]::IsNullOrEmpty($Database) -eq $False) -and ([String]::IsNullOrWhiteSpace($Database) -eq $False)}
                              {
                                  $DBConnectionStringBuilder.PSBase.InitialCatalog = $Database
                              }

                            {([String]::IsNullOrEmpty($ExportDirectory) -eq $True) -and ([String]::IsNullOrWhiteSpace($ExportDirectory) -eq $True)}
                              {
                                  [System.IO.DirectoryInfo]$ExportDirectory = "$($Env:Public)\Documents\Reports\$($CmdletName)"
                              }   
                        }

                    $DBConnection = [System.Data.SQLClient.SQLConnection]::New()
        
                    Switch (([String]::IsNullOrEmpty($UserID) -eq $True) -or ([String]::IsNullOrWhiteSpace($UserID) -eq $True) -or ([String]::IsNullOrEmpty($Password) -eq $True) -or ([String]::IsNullOrWhiteSpace($Password) -eq $True))
                      {
                          {($_ -eq $True)}
                            {
                                $DBConnectionStringBuilder.PSBase.IntegratedSecurity = $True
                            }
                            
                          Default
                            {
                                $DBConnectionStringBuilder.PSBase.IntegratedSecurity = $False
          
                                $Credential = [System.Management.Automation.PSCredential]::New($UserID, (ConvertTo-SecureString -String $Password -AsPlainText -Force))

                                $Null = $Credential.Password.MakeReadOnly()
          
                                $DBConnectionCredential = [System.Data.SQLClient.SQLCredential]::New($Credential.UserName, $Credential.Password)
                  
                                $DBConnection.Credential = ($DBConnectionCredential)
                            }
                      }

                    $DBConnectionStringBuilder.PSBase.DataSource = $DataSourceBuilder.ToString()
        
                    $DBConnectionString = $DBConnectionStringBuilder.ConnectionString
                  
                    $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "Database Connection String = $($DBConnectionString)"
                    Write-Verbose -Message ($LoggingDetails.LogMessage)
               
                    $DBConnection.ConnectionString = ($DBConnectionString)

                    $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to open the database connection. Please Wait..."
                    Write-Verbose -Message $LogMessage

                    $DBConnection.Open()
                }
              Catch
                {
                    $ErrorHandlingDefinition.Invoke()
                }
          }

        Process
          {           
              For ($DBQueryListIndex = 0; $DBQueryListIndex -lt $DBQueryList.Count; $DBQueryListIndex++)
                {
                    Try
                      {  
                          [String]$DBQuery = $DBQueryList[$DBQueryListIndex]
                          [Int]$DBQueryNumber = $DBQueryListIndex + 1
                          
                          $DBCommand = [System.Data.SQLClient.SQLCommand]::New()
                            $DBCommand.Connection = ($DBConnection)
                            $DBCommand.CommandText = ($DBQuery)

                          $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "Attempting to execute database query #$($DBQueryNumber). Please Wait..."
                          Write-Verbose -Message ($LoggingDetails.LogMessage)
        
                          $Null = $DBCommand.ExecuteScalar()
      
                          $DBQueryResults = [System.Data.DataTable]::New()
      
                          $DBDataAdapter = [System.Data.SQLClient.SQLDataAdapter]::New($DBCommand)
                            $Null = $DBDataAdapter.Fill($DBQueryResults)

                          $DBQueryRowCount = $DBQueryResults.Rows.Count

                          $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "$($DBQueryRowCount) row(s) were returned from the database query."
                          Write-Verbose -Message ($LoggingDetails.LogMessage)

                          Switch ($DBQueryRowCount -gt 0)
                            {
                                {($_ -eq $True)}
                                  {          
                                      $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "Database Query Column Names = $($DBQueryResults.Columns.ColumnName -Join ', ')"
                                      Write-Verbose -Message ($LoggingDetails.LogMessage)
                                  }
                            }

                          [String]$ResultSetNumber = ($DBQueryNumber).ToString('000')
                          [String]$ResultSetPropertyName = "ResultSet$($ResultSetNumber)"

                          $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "Attempting to add the result(s) from database query #$($DBQueryNumber) to property `"$($ResultSetPropertyName)`". Please Wait..."
                          Write-Verbose -Message ($LoggingDetails.LogMessage)

                          $PropertyInclusionList = [System.Collections.Generic.List[Object]]@()
                            $Null = $PropertyInclusionList.Add('*')
                          
                          $PropertyExclusionList = [System.Collections.Generic.List[Object]]@()
                            $Null = $PropertyExclusionList.AddRange(('HasErrors', 'ItemArray', 'RowError', 'RowState', 'Table'))

                          $DBQueryRows = $DBQueryResults.Rows | Select-Object -Property ($PropertyInclusionList) -ExcludeProperty ($PropertyExclusionList)

                          $Null = $OutputObjectProperties.Add($ResultSetPropertyName, $DBQueryRows)
                      }
                    Catch
                      {
                          $ErrorHandlingDefinition.Invoke()
                      }
                    Finally
                      {          
                          Try
                            {
                                $Null = $DBQueryResults.Dispose()
                                $Null = $DBDataAdapter.Dispose()
                            }
                          Catch
                            {
                            
                            }
                      }
                }    
          }
        
        End
          {                                        
              Try
                {
                    #Close the database connection
                      $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to close the database connection. Please Wait..."
                      Write-Verbose -Message $LogMessage

                      $Null = $DBConnection.Close()
                    
                    #Create the powershell object that will be exported to the powershell pipeline
                      $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties)

                    #If specified, export each result set to a separate JSON file
                      Switch ($True)
                        {
                            {($ExportJSON.IsPresent)}
                              {
                                  $ExportParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
                                    $ExportParameters.Add('Compress', $False)
                                    $ExportParameters.Add('Encoding', [System.Text.Encoding]::Default)
                                    $ExportParameters.Add('Depth', 10)
                                  
                                  [String[]]$OutputObjectPropertyList = $OutputObject.PSObject.Properties.Name

                                  For ($OutputObjectPropertyListIndex = 0; $OutputObjectPropertyListIndex -lt $OutputObjectPropertyList.Count; $OutputObjectPropertyListIndex++)
                                    {
                                        [String]$OutputObjectPropertyName = $OutputObjectPropertyList[$OutputObjectPropertyListIndex]

                                        [System.IO.FileInfo]$OutputObjectExportPath = "$($ExportDirectory.FullName)\$($OutputObjectPropertyName).json"

                                        Switch ([System.IO.File]::Exists($OutputObjectExportPath.FullName))
                                          {
                                              {($_ -eq $True)}
                                                {
                                                    [String]$JSONContents = [System.IO.File]::ReadAllText($OutputObjectExportPath.FullName, $ExportParameters.Encoding)

                                                    $JSONObject = ConvertFrom-JSON -InputObject ($JSONContents)

                                                    $OutputObjectPropertyValue = $OutputObject.$($OutputObjectPropertyName)

                                                    $JSONObject.$($OutputObjectPropertyName) = ($OutputObjectPropertyValue)

                                                    [String]$OutputObjectPropertyValueAsJSON = $JSONObject | ConvertTo-JSON -Depth ($ExportParameters.Depth) -Compress:($ExportParameters.Compress)

                                                    $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "Attempting to update the result set `"$($OutputObjectPropertyName)`" within the file of `"$($OutputObjectExportPath.FullName)`". Please Wait..."
                                                    Write-Verbose -Message ($LoggingDetails.LogMessage)
                                                    
                                                    $Null = [System.IO.File]::WriteAllText($OutputObjectExportPath.FullName, $OutputObjectPropertyValueAsJSON, $ExportParameters.Encoding)
                                                }
                            
                                              {($_ -eq $False)}
                                                {
                                                    $OutputObjectPropertyValue = $OutputObject | Select-Object -Property @($OutputObjectPropertyName)
                                                    
                                                    [String]$OutputObjectPropertyValueAsJSON = $OutputObjectPropertyValue | ConvertTo-JSON -Depth ($ExportParameters.Depth) -Compress:($ExportParameters.Compress)
                                                    
                                                    Switch ([System.IO.Directory]::Exists($OutputObjectExportPath.Directory.FullName))
                                                      {
                                                          {($_ -eq $False)}
                                                            {
                                                                $Null = [System.IO.Directory]::CreateDirectory($OutputObjectExportPath.Directory.FullName)
                                                            }
                                                      }
                                                    
                                                    $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - " + "Attempting to export the result set `"$($OutputObjectPropertyName)`" to the file of `"$($OutputObjectExportPath.FullName)`". Please Wait..."
                                                    Write-Verbose -Message ($LoggingDetails.LogMessage)

                                                    $Null = [System.IO.File]::WriteAllText($OutputObjectExportPath.FullName, $OutputObjectPropertyValueAsJSON, $ExportParameters.Encoding)
                                                }
                                          }
                                    }
                              }
                        }
                    
                    #Write the object to the powershell pipeline 
                      Write-Output -InputObject ($OutputObject)
                
                    #Determine the date and time the function completed execution
                      $FunctionEndTime = (Get-Date)

                      $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($CmdletName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))"
                      Write-Verbose -Message $LogMessage

                    #Log the total script execution time  
                      $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime)

                      $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)"
                      Write-Verbose -Message $LogMessage
                    
                    $LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($CmdletName)`' is completed."
                    Write-Verbose -Message $LogMessage
                }
              Catch
                {
                    $ErrorHandlingDefinition.Invoke()
                }
          }
    }
#endregion