functions/Find-DBValue.ps1

function Find-DBValue {
    <#
    .SYNOPSIS
        Scans a database for a value. Returns all tables and columns that contain that value, as well as the value itself.
 
    .DESCRIPTION
        Scans a database for a value. Returns all tables and columns that contain that value, as well as the value itself.
 
    .PARAMETER ServerInstance
        The sql server instance to connect to.
 
    .PARAMETER Database
        Specifies the name of the database.
 
    .PARAMETER Credentials
        Specifies credentials to connect to the database with. If not supplied then a trusted connection will be used.
 
    .PARAMETER LookForValue
        The value to search for in the database. This string supports LIKE clause syntax.
 
    .PARAMETER LookForValueType
        The type of value being looked for. The valid values are "string" and "number". Use the appropriate one to scan the
        correct type of columns for the value you are looking for. Defaults to "string".
 
    .PARAMETER IncludeMaxWidthColumns
        Max width columns are not scanned by default unless this switch is enabled.
 
    .PARAMETER IncludeSchemas
        A list of schemas to include in the results. If not provided then all schemas will be returned.
 
    .PARAMETER ExcludeSchemas
        A list of schemas to exclude from the results.
 
    .PARAMETER IncludeTables
        A list of tables to include in the results. If not provided then all tables will be returned.
 
    .PARAMETER ExcludeTables
        A list of tables to exclude from the results.
 
    .PARAMETER MaxThreads
        The max number of threads to run sets of queries with. Defaults to 6.
 
    .OUTPUTS
 
    .NOTES
        More info on LIKE clause syntax: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql?view=sql-server-ver16#arguments
 
    .EXAMPLE
        Scans all string columns in all user defined tables in the dbo schema for the value "%tim%"
        PS> Find-DBValue -ServerInstance "ServerName" -Database "DBName" -LookForValue "%tim%" -IncludeSchemas @("dbo") | Format-Table
 
    .LINK
        https://github.com/tcartwright/tcdbtools
 
    .NOTES
        Author: Tim Cartwright
 
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$ServerInstance,
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Database,
        [pscredential]$Credentials,
        [Parameter(Mandatory=$true)]
        [ValidateLength(2, 256)]
        [ValidateNotNullOrEmpty()]
        [string]$LookForValue,
        [ValidateSet("number", "string")]
        [string]$LookForValueType = "string",
        [switch]$IncludeMaxWidthColumns,
        [string[]]$IncludeSchemas,
        [string[]]$ExcludeSchemas,
        [string[]]$IncludeTables,
        [string[]]$ExcludeTables,
        [ValidateRange(1, 16)]
        [int]$MaxThreads = 6
    )

    begin {
        $ret = @()
        #$ret = New-Object System.Collections.ArrayList

        $connection = New-DBSQLConnection -ServerInstance $ServerInstance -Database $Database -Credentials $Credentials -MultipleActiveResultSets
        $jobPrefix = "Scan For Value"

        $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
        $RunspacePool.Open()
        # $Jobs = @()
        $jobs = New-Object System.Collections.ArrayList
    }

    process {
        try {
            <#
            # FIND ALL THE POSSIBLE APPROPRIATE COLUMNS THAT COULD CONTAIN THE VALUE
            #>

            $connection.Open()
            $sql = (GetSQLFileContent -fileName "ScanForValueColumns.sql")
            $where = ""
            $parameters = @(
                (New-DBSqlParameter -name "lookFor" -type VarChar -size 256 -value $LookForValue),
                (New-DBSqlParameter -name "lookForType" -type VarChar -size 10 -value $LookForValueType),
                (New-DBSqlParameter -name "includeMaxLengthColumns" -type Bit -value $IncludeMaxWidthColumns.IsPresent)
            )
            if ($IncludeSchemas) {
                $params = Get-DBInClauseParams -prefix "is" -values $IncludeSchemas -type NVarChar -size 256
                $where += "`r`n`tAND OBJECT_SCHEMA_NAME(t.object_id) IN ($(Get-DBInClauseString -parameters $params))"
                $parameters += $params
            }
            if ($ExcludeSchemas) {
                $params = Get-DBInClauseParams -prefix "es" -values $ExcludeSchemas -type NVarChar -size 256
                $where += "`r`n`tAND OBJECT_SCHEMA_NAME(t.object_id) NOT IN ($(Get-DBInClauseString -parameters $params))"
                $parameters += $params
            }
            if ($IncludeTables) {
                $params = Get-DBInClauseParams -prefix "it" -values $IncludeTables -type NVarChar -size 256
                $paramStr = Get-DBInClauseString -parameters $params -delimiter "), OBJECT_ID("
                $paramStr = "OBJECT_ID($($paramStr))"
                $where += "`r`n`tAND t.object_id IN ($paramStr)"
                $parameters += $params
            }
            if ($ExcludeTables) {
                $params = Get-DBInClauseParams -prefix "et" -values $ExcludeTables -type NVarChar -size 256
                $paramStr = Get-DBInClauseString -parameters $params -delimiter "), OBJECT_ID("
                $paramStr = "OBJECT_ID($($paramStr))"
                $where += "`r`n`tAND t.object_id NOT IN ($paramStr)"
                $parameters += $params
            }
            $sql = $sql -ireplace "--<<extra_where>>", $where
            Write-Verbose $sql
            $queryResults = Invoke-DBDataTableQuery -conn $connection -sql $sql -parameters $parameters

            <#
            # START THE JOBS USING CHUNKS OF THE RESULTS
            #>


            $skip = 0
            $take = 10
            $counter = 1
            [int]$total = $queryResults.Rows.Count / $take
            if ($queryResults.Rows.Count % $take -gt 0) { $total++ }
            $activity = "Starting $($total) jobs"

            $takeResults = $queryResults | Select-Object -Skip $skip -First $take

            while ($takeResults) {
                $sql = $takeResults.Query -join "`r`n"
                # rip off the last union all
                $sql = "$($sql.SubString(0, $sql.Length - 10))"
                $parameters = @(
                    (New-DBSqlParameter -name "lookFor" -type VarChar -size 256 -value $LookForValue)
                )

                $jobName = "$jobPrefix - $counter"
                Write-Verbose "JOB $jobName SQL: `r`n$sql`r`n`r`n"

                Write-Progress -Activity $activity `
                    -Status “Starting Job $jobName of $total” `
                    -PercentComplete (GetPercentComplete -counter $counter -total $total)

                $PowerShell = [powershell]::Create()
                $PowerShell.RunspacePool = $RunspacePool
                $PowerShell.AddScript($QueryTableScriptBlock).AddArgument($connection).AddArgument($sql).AddArgument($parameters) | Out-Null
                $Handle = $PowerShell.BeginInvoke()
                $temp = "" | Select-Object Name, PowerShell, Handle
                $temp.Name = $jobName
                $temp.PowerShell = $PowerShell
                $temp.Handle = $Handle
                $jobs.Add($temp) | Out-Null

                $counter++

                $skip += $take
                $takeResults = $queryResults | Select-Object -Skip $skip -First $take
            }

            Write-Progress -Activity $activity -Completed

            <#
            # START SCANNING THE JOBS WAITING FOR THEM TO FINISH
            #>


            $counter = 0
            $activity = "Waiting for $($total) jobs to finish"

            while ($Jobs.Handle.IsCompleted -contains $false) {
                $counter = ($Jobs | Where-Object { $_.Handle.IsCompleted }).Count

                Write-Progress -Activity $activity `
                    -Status “Job(s) $counter of $total done” `
                    -PercentComplete (GetPercentComplete -counter $counter -total $total)

                Start-Sleep -Milliseconds 500
            }

            Write-Progress -Activity $activity -Completed

            <#
            # NOW GATHER THE RESULTS OF THE JOBS
            #>


            foreach($job in $jobs) {
                $result = $job.Powershell.EndInvoke($job.Handle)
                $job.Powershell.Dispose()

                if ($result) {
                    $ret += $result
                }
            }

            return $ret |
                Sort-Object server_name, db_name, schema_name, table_name, column_name |
                Select-Object db_name, schema_name, table_name, column_name, where_clause, value
        } finally {
            if ($connection) { $connection.Dispose() }
        }
    }

    end {
        if ($RunspacePool) { $RunspacePool.Dispose() }
    }
}