SqlQueryHelper.psm1

[string[]]$PassThruSqlcmdParam = @('Server', 'Database', 'Credential', 'CommandTimeout', 'OnlyShowQuery')
$q = "'"
$qq = "''"

[System.Management.Automation.PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Add('StringList', 'System.Collections.Generic.List[System.String]')
# [System.Management.Automation.PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Add('ObjectList', 'System.Collections.Generic.List[System.Object]')

Function Invoke-SqlCommand {
    <#
.EXAMPLE
Invoke-SqlCommand -Server sql -Database test1 -Query "SELECT Name,ServiceName FROM ps WHERE Name LIKE '%time%'"
 
Name ServiceName
---- -----------
autotimesvc autotimesvc
TimeBrokerSvc TimeBrokerSvc
vmictimesync vmictimesync
W32Time W32Time
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)][Alias('ServerInstance', 'SqlServer')][string]$Server
        , [Parameter(Mandatory = $true)][Alias('SqlDatabase')][string]$Database
        , [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string[]]$Query
        , [System.Management.Automation.PSCredential]$Credential
        , [Alias('QueryTimeout')][int]$CommandTimeout = 30
        , [switch]$OnlyShowQuery
        , [switch]$Raw
    )
    Begin {
        $Counter = 1

        $SqlConnectionStringBuilderProperties = @{
            InitialCatalog      = $Database
            DataSource          = $Server
            IntegratedSecurity  = $true
            PersistSecurityInfo = $true
        }

        if ($Credential) {
            $SqlConnectionStringBuilderProperties['IntegratedSecurity'] = $false
            $SqlConnectionStringBuilderProperties['UserID'] = $Credential.UserName.TrimStart('\')
            $SqlConnectionStringBuilderProperties['Password'] = $Credential.GetNetworkCredential().Password
        }

        $SqlConnectionStringBuilder = New-Object -TypeName System.Data.SqlClient.SqlConnectionStringBuilder -Property $SqlConnectionStringBuilderProperties
        $ConnectionString = $SqlConnectionStringBuilder.ConnectionString

        $SqlDataAdapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter -Property @{ `
                SelectCommand = New-Object -TypeName System.Data.SqlClient.SqlCommand -Property @{ `
                    CommandTimeout = $CommandTimeout; `
                    Connection     = New-Object -TypeName System.Data.SqlClient.SqlConnection -Property @{ `
                        ConnectionString = $ConnectionString; `

                }
            }
        }

        if (!$OnlyShowQuery) {
            $SqlDataAdapter.SelectCommand.Connection.Open()
        }
    }
    Process {
        $Query | % {
            if (!$OnlyShowQuery) {
                Write-Verbose -Message "ConnectionState: $($SqlDataAdapter.SelectCommand.Connection.State)"
            }

            Write-Verbose -Message "CommandNumber: $Counter"

            if ($OnlyShowQuery) {
                $_
            }
            else {
                $SqlDataAdapter.SelectCommand.CommandText = $_
                $DataSet = New-Object -TypeName System.Data.DataSet
                $RowCount = $SqlDataAdapter.Fill($DataSet)
                Write-Verbose -Message "ReturnedRows: $RowCount"
                Write-Verbose -Message "Query: $_"
                Write-Debug -Message "ConnectionString: '$ConnectionString'"
                if ($Raw) {
                    $DataSet
                }
                else {
                    $DataSet.Tables | % { $_.Rows }
                }
            }
            $Counter++
        }
    }
    End {
        if ($SqlDataAdapter.SelectCommand.Connection.State -ne [System.Data.ConnectionState]::Closed) {
            $SqlDataAdapter.SelectCommand.Connection.Close()
        }
    }
}

Function Add-SqlTable {
    <#
.EXAMPLE
Get-Service | Create-SqlTable -Server sql -Database [test1] -Table [ps] -OnlyShowQuery
CREATE TABLE ps (
        [Name] nvarchar(62) NOT NULL,
        [RequiredServices] nvarchar(MAX) NOT NULL,
        [CanPauseAndContinue] nvarchar(8) NOT NULL,
        [CanShutdown] nvarchar(8) NOT NULL,
        [CanStop] nvarchar(6) NOT NULL,
        [DisplayName] nvarchar(178) NOT NULL,
        [DependentServices] nvarchar(MAX) NULL,
        [MachineName] nvarchar(2) NOT NULL,
        [ServiceName] nvarchar(62) NOT NULL,
        [ServicesDependedOn] nvarchar(MAX) NOT NULL,
        [ServiceHandle] nvarchar(MAX) NULL,
        [Status] nvarchar(10) NOT NULL,
        [ServiceType] nvarchar(51) NOT NULL,
        [StartType] nvarchar(9) NOT NULL,
        [Site] nvarchar(MAX) NULL,
        [Container] nvarchar(MAX) NULL
) ON [PRIMARY]
#>

    [CmdletBinding()]
    [Alias('Create-SqlTable', 'New-SqlTable')]
    param(
        [Parameter(Mandatory = $true)][Alias('ServerInstance', 'SqlServer')][String]$Server
        , [Parameter(Mandatory = $true)][Alias('SqlDatabase')][String]$Database
        , [Parameter(Mandatory = $true)][Alias('SqlTable')][String]$Table
        , [string]$IdentityName
        , [double]$StringReserveMultiple
        , [int]$TruncateString = 4000
        , [System.Array]$IdentitySettings = @(1, 1)
        , [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({ @($_.psobject.Properties).Count })]
        [Alias('Data')]
        [System.Array]$InputObject
        , [switch]$OnlyShowQuery
    )

    Begin {
        $PSBoundParameters.Keys | ? { $PassThruSqlcmdParam.Contains($_) } | % -Begin { $SqlQueryParam = @{} } -Process { $SqlQueryParam[$_] = $PSBoundParameters[$_] }
        $InputObjects = New-Object -TypeName System.Collections.ArrayList
        $Properties = New-Object -TypeName System.Collections.ArrayList
        [string]$ReturnChar = [string][char]10
        [string]$JoinChar = ',' + [string][char]10
        [string]$TabChar = [string][char]9
        $PSBoundParameters.Keys | ? { $('StringReserveMultiple', 'TruncateString').Contains($_) } | % -Begin { $ImportDataParam = @{} } -Process { $ImportDataParam[$_] = $PSBoundParameters[$_] }
    }
    Process {
        $InputObject | % {
            [void]$InputObjects.Add($_)

            $_.psobject.Properties | % {
                if (!$Properties.Contains($_.Name)) {
                    [void]$Properties.Add($_.Name)
                }
            }
        }
    }
    End {
        $TableTypes = $InputObjects | Select-Object -Property $Properties | ConvertTo_HashTable | Import_Data -CreateTable @ImportDataParam
        $Types = $TableTypes.Types
        $Query = $Properties | % -Begin {
            [string[]]$Lines = @()
            if ($IdentityName) { $Lines += ($TabChar + "[$IdentityName] [int] IDENTITY($($IdentitySettings -join ',')) NOT NULL") }
            "CREATE TABLE $Table (" + $ReturnChar
        } -Process {
            if ($Types.ContainsKey($_)) {
                $Lines += ($TabChar + "[$_] " + $Types[$_]['Type'] + @{$true = ' NULL'; $false = ' NOT NULL' }[$Types[$_]['AllowNull']])
            }
        } -End {
            $Lines -join $JoinChar
            $ReturnChar
            ') ON [PRIMARY]'
        }

        Invoke-SqlCommand @SqlQueryParam -Query (-join $Query)
    }
}

Function Add-SqlRecord {
    <#
.EXAMPLE
Get-Service | select -f 3 | Insert-SqlRecord -Server sql -Database test1 -Table ps -PassThru | select Name,ServiceName
Name ServiceName
---- -----------
AarSvc_45a9d9 AarSvc_45a9d9
AJRouter AJRouter
ALG ALG
.EXAMPLE
Get-Service | select -f 3 -p Name,ServiceName | Insert-SqlRecord -Server sql -Database test1 -Table ps -PassThru -OnlyShowQuery
 INSERT INTO ps ([Name],[ServiceName]) OUTPUT INSERTED.* VALUES ('AarSvc_45a9d9','AarSvc_45a9d9')
 INSERT INTO ps ([Name],[ServiceName]) OUTPUT INSERTED.* VALUES ('AJRouter','AJRouter')
 INSERT INTO ps ([Name],[ServiceName]) OUTPUT INSERTED.* VALUES ('ALG','ALG')
.EXAMPLE
Get-Service | ? {!$_.DependentServices} | select -f 3 -p Name,ServiceName,DependentServices | Insert-SqlRecord -Server sql -Database test1 -Table ps -PassThru -OnlyShowQuery
 INSERT INTO ps ([ServiceName],[Name],[DependentServices]) OUTPUT INSERTED.* VALUES ('AarSvc_45a9d9','AarSvc_45a9d9','')
 INSERT INTO ps ([ServiceName],[Name],[DependentServices]) OUTPUT INSERTED.* VALUES ('AJRouter','AJRouter','')
 INSERT INTO ps ([ServiceName],[Name],[DependentServices]) OUTPUT INSERTED.* VALUES ('ALG','ALG','')
.EXAMPLE
Get-Service | ? {!$_.DependentServices} | select -f 3 -p Name,ServiceName,DependentServices | Insert-SqlRecord -Server sql -Database test1 -Table ps -PassThru -OnlyShowQuery -SkipNullOrEmpty -SkipNullOrWhiteSpace
 INSERT INTO ps ([Name],[ServiceName]) OUTPUT INSERTED.* VALUES ('AarSvc_45a9d9','AarSvc_45a9d9')
 INSERT INTO ps ([Name],[ServiceName]) OUTPUT INSERTED.* VALUES ('AJRouter','AJRouter')
 INSERT INTO ps ([Name],[ServiceName]) OUTPUT INSERTED.* VALUES ('ALG','ALG')
.EXAMPLE
Get-Service | % {Update-SqlRecord -Server sql -Database test1 -Table ps -Data $_ -Filter @{ServiceName = $_.ServiceName} -PassThru -InsertIfNotFound}
ACTION Name
------ ----
UPDATE MicrosoftEdgeElevationService
UPDATE MicrosoftSearchInBing
INSERT W32Time
 
#>

    [CmdletBinding()]
    [Alias('New-SqlRecord', 'Insert-SqlRecord')]
    param (
        [Parameter(Mandatory = $true)][Alias('ServerInstance', 'SqlServer')][String]$Server
        , [Parameter(Mandatory = $true)][Alias('SqlDatabase')][String]$Database
        , [Parameter(Mandatory = $true)][Alias('SqlTable')][String]$Table
        , [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript({ @($_.psobject.Properties).Count })]
        [Alias('Data')]
        [System.Array]$InputObject
        , [alias('RowCount')][int]$Limit
        , [Parameter(Mandatory = $false)][switch]$PassThru
        , [System.Management.Automation.PSCredential]$Credential
        , [int]$CommandTimeout
        , [string[]]$Property
        , [switch]$SkipNullOrEmpty
        , [switch]$SkipNullOrWhiteSpace
        , [int]$TruncateString = 4000
        , [switch]$OnlyShowQuery
    )

    Begin {
        $PSBoundParameters.Keys | ? { $PassThruSqlcmdParam.Contains($_) } | % -Begin { $SqlQueryParam = @{} } -Process { $SqlQueryParam[$_] = $PSBoundParameters[$_] }
        $PSBoundParameters.Keys -like 'SkipNull*' | % -Begin { $ConvertToHashTableParam = @{} } -Process { $ConvertToHashTableParam[$_] = [bool]($PSBoundParameters[$_].IsPresent) }
        if ($PSBoundParameters.ContainsKey('Property')) { $ConvertToHashTableParam['Property'] = $Property }
        $PSBoundParameters.Keys | ? { $('CreateTable', 'TruncateString').Contains($_) } | % -Begin { $ImportDataParam = @{} } -Process { $ImportDataParam[$_] = $PSBoundParameters[$_] }
        $OUTPUT = @{$true = 'OUTPUT INSERTED.*'; $false = '' }[$PSBoundParameters.ContainsKey('PassThru')]
        if ($Limit) {
            $RowCount = "SET ROWCOUNT $Limit;"
        }
        else {
            $RowCount = ''
        }
    }

    Process {
        $InputObject | % {
            if ($_.psobject.TypeNames -like '*System.Collections*') {
                if (!$ConvertToHashTableParam.Count) {
                    $Data = $_
                }
                else {
                    $Data = New-Object -TypeName PSCustomObject -Property $_ | ConvertTo_HashTable @ConvertToHashTableParam
                }
            }
            else {
                $Data = $_ | ConvertTo_HashTable @ConvertToHashTableParam
            }

            $iData = Import_Data -HashTable $Data @ImportDataParam
            $DataKeys = $iData.Keys
            $DataValues = $iData.Values

            if ($DataKeys) {
                $columns = $DataKeys -join '],['
                $values = $DataValues -join ","
                Invoke-SqlCommand @SqlQueryParam -Query "$RowCount INSERT INTO $Table ([$columns]) $OUTPUT VALUES ($values)"
            }
            else {
                Write-Warning -Message "null data object: $($_ | ConvertTo-Json -Compress)"
            }
        }
    }

    End {}
}

Function Edit-SqlRecord {
    <#
.EXAMPLE
Get-Service | ? {!$_.DependentServices} | select -f 3 -p Name,ServiceName,DependentServices | % {Update-SqlRecord -Server sql -Database test1 -Table ps -Data $_ -Filter @{ServiceName = $_.ServiceName} -PassThru -SkipNullOrWhiteSpace}
 UPDATE ps SET [Name] = 'AarSvc_45a9d9',[ServiceName] = 'AarSvc_45a9d9' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = 'AarSvc_45a9d9'
 UPDATE ps SET [Name] = 'AJRouter',[ServiceName] = 'AJRouter' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = 'AJRouter'
 UPDATE ps SET [Name] = 'ALG',[ServiceName] = 'ALG' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = 'ALG'
.EXAMPLE
Get-Service | ? {!$_.DependentServices} | select -f 3 -p Name,ServiceName,DependentServices | % {Update-SqlRecord -Server sql -Database test1 -Table ps -Data $_ -FilterString "[ServiceName] = $($_.ServiceName)" -PassThru -OnlyShowQuery -SkipNullOrWhiteSpace}
 UPDATE ps SET [Name] = 'AarSvc_45a9d9',[ServiceName] = 'AarSvc_45a9d9' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = AarSvc_45a9d9
 UPDATE ps SET [Name] = 'AJRouter',[ServiceName] = 'AJRouter' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = AJRouter
 UPDATE ps SET [Name] = 'ALG',[ServiceName] = 'ALG' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = ALG
.EXAMPLE
Get-Service | ? {!$_.DependentServices} | select -f 3 -p Name,ServiceName,DependentServices | % {Update-SqlRecord -Server sql -Database test1 -Table ps -Data $_ -FilterString "[ServiceName] = $($_.ServiceName)" -PassThru -SkipNullOrWhiteSpace -InsertIfNotFound -OnlyShowQuery}
 UPDATE ps SET [Name] = 'AarSvc_45a9d9',[ServiceName] = 'AarSvc_45a9d9' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = AarSvc_45a9d9 IF @@ROWCOUNT = 0 INSERT INTO ps ([Name],[ServiceName]) OUTPUT 'INSERT' AS [ACTION], INSERTED.* VALUES ('AarSvc_45a9d9','AarSvc_45a9d9')
 UPDATE ps SET [Name] = 'AJRouter',[ServiceName] = 'AJRouter' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = AJRouter IF @@ROWCOUNT = 0 INSERT INTO ps ([Name],[ServiceName]) OUTPUT 'INSERT' AS [ACTION], INSERTED.* VALUES ('AJRouter','AJRouter')
 UPDATE ps SET [Name] = 'ALG',[ServiceName] = 'ALG' OUTPUT 'UPDATE' AS [ACTION], INSERTED.* WHERE [ServiceName] = ALG IF @@ROWCOUNT = 0 INSERT INTO ps ([Name],[ServiceName]) OUTPUT 'INSERT' AS [ACTION], INSERTED.* VALUES ('ALG','ALG')
#>

    [CmdletBinding(DefaultParameterSetName = 'Filter')]
    [Alias('Update-SqlRecord', 'Set-SqlRecord')]
    param (
        [Parameter(Mandatory = $true)][Alias('ServerInstance', 'SqlServer')][String]$Server
        , [Parameter(Mandatory = $true)][Alias('SqlDatabase')][String]$Database
        , [Parameter(Mandatory = $true)][Alias('SqlTable')][String]$Table
        , [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript({ @($_.psobject.Properties).Count })]
        [Alias('Data')]
        [System.Array]$InputObject
        , [Parameter(Mandatory = $true, ParameterSetName = 'Filter')]
        [ValidateScript({ @($_.psobject.Properties).Count })]
        [HashTable]$Filter
        , [Parameter(Mandatory = $false, ParameterSetName = 'Filter')][ValidateSet('AND', 'OR')][string]$FilterCondition = 'AND'
        , [Parameter(Mandatory = $true, ParameterSetName = 'FilterString')][string]$FilterString
        , [Parameter(Mandatory = $false)][switch]$InsertIfNotFound
        , [alias('RowCount')][int]$Limit
        , [Parameter(Mandatory = $false)][switch]$PassThru
        , [System.Management.Automation.PSCredential]$Credential
        , [int]$CommandTimeout
        , [string[]]$Property
        , [switch]$SkipNullOrEmpty
        , [switch]$SkipNullOrWhiteSpace
        , [int]$TruncateString = 4000
        , [switch]$OnlyShowQuery
    )

    Begin {
        $PSBoundParameters.Keys | ? { $PassThruSqlcmdParam.Contains($_) } | % -Begin { $SqlQueryParam = @{} } -Process { $SqlQueryParam[$_] = $PSBoundParameters[$_] }
        $PSBoundParameters.Keys -like 'SkipNull*' | % -Begin { $ConvertToHashTableParam = @{} } -Process { $ConvertToHashTableParam[$_] = [bool]($PSBoundParameters[$_].IsPresent) }
        if ($PSBoundParameters.ContainsKey('Property')) { $ConvertToHashTableParam['Property'] = $Property }
        $PSBoundParameters.Keys | ? { $('CreateTable', 'TruncateString').Contains($_) } | % -Begin { $ImportDataParam = @{} } -Process { $ImportDataParam[$_] = $PSBoundParameters[$_] }
        $OUTPUT = @{$true = @{UPDATE = "OUTPUT 'UPDATE' AS [ACTION], INSERTED.*"; INSERT = "OUTPUT 'INSERT' AS [ACTION], INSERTED.*" }; $false = @{} }[$PSBoundParameters.ContainsKey('PassThru')]
        if ($Limit) {
            $RowCount = "SET ROWCOUNT $Limit;"
        }
        else {
            $RowCount = ''
        }

        [string[]]$Queries = @()
    }

    Process {
        $InputObject | % {
            if ($_.psobject.TypeNames -like 'System.Collections.*') {
                if (!$ConvertToHashTableParam.Count) {
                    $Data = $_
                }
                else {
                    $Data = New-Object -TypeName PSCustomObject -Property $_ | ConvertTo_HashTable @ConvertToHashTableParam
                }
            }
            else {
                $Data = $_ | ConvertTo_HashTable @ConvertToHashTableParam
            }

            $iData = Import_Data -HashTable $Data
            $DataKeys = $iData.Keys
            $DataValues = $iData.Values

            $columns = $DataKeys -join '],['
            $values = $DataValues -join ","

            [string[]]$sets = @()
            for ($i = 0; $i -lt $DataKeys.Count; $i++) {
                $sets += "[$($DataKeys[$i])] = $($DataValues[$i])"
            }

            $set = $sets -join ','

            if ($Filter) {
                $iFilter = Import_Data -HashTable $Filter
                $FilterKeys = $iFilter.Keys
                $FilterValues = $iFilter.Values

                [string[]]$wheres = @()
                for ($i = 0; $i -lt $FilterKeys.Count; $i++) {
                    $key = $FilterKeys[$i]
                    $value = $FilterValues[$i].Replace('*', '%')
                    $operator = if ($value -like '*%*') { 'like' } else { '=' }
                    $wheres += "[$key] $operator $value"
                }

                $where = $wheres -join " $FilterCondition "
            }
            else {
                $where = $FilterString
            }

            if ($InsertIfNotFound) {
                $Query = "$RowCount UPDATE $Table SET $set $($OUTPUT['UPDATE']) WHERE $where IF @@ROWCOUNT = 0 INSERT INTO $Table ([$columns]) $($OUTPUT['INSERT']) VALUES ($values)"
            }
            else {
                $Query = "$RowCount UPDATE $Table SET $set $($OUTPUT['UPDATE']) WHERE $where"
            }
            if ($DataKeys) {
                $Queries += $Query
            }
            else {
                Write-Warning -Message "null data object: $($_ | ConvertTo-Json -Compress)"
            }
        }
    }

    End {
        Invoke-SqlCommand @SqlQueryParam -Query $Queries
    }
}

Function Remove-SqlRecord {
    <#
.EXAMPLE
Remove-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Filter @{ServiceName = 'w32time'} -PassThru -OnlyShowQuery
 DELETE ps OUTPUT DELETED.* WHERE [ServiceName] = 'w32time'
.EXAMPLE
Remove-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Filter @{ServiceName = 'w32time'} -PassThru
 
Name RequiredServices CanPauseAndContinue CanShutdown CanStop DependentServices MachineName ServiceName ServicesDependedOn
---- ---------------- ------------------- ----------- ------- ----------------- ----------- ----------- ------------------
W32Time False False False . W32Time
.EXAMPLE
Remove-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -FilterString "[ServiceName] LIKE 'w32time'" -PassThru
 
Name RequiredServices CanPauseAndContinue CanShutdown CanStop DependentServices MachineName ServiceName ServicesDependedOn
---- ---------------- ------------------- ----------- ------- ----------------- ----------- ----------- ------------------
W32Time False False False . W32Time
#>

    [CmdletBinding(DefaultParameterSetName = 'Filter')]
    param (
        [Parameter(Mandatory = $true)][Alias('ServerInstance', 'SqlServer')][String]$Server
        , [Parameter(Mandatory = $true)][Alias('SqlDatabase')][String]$Database
        , [Parameter(Mandatory = $true)][Alias('SqlTable')][String]$Table
        , [Parameter(Mandatory = $true, ParameterSetName = 'Filter')][Hashtable]$Filter
        , [Parameter(Mandatory = $false, ParameterSetName = 'Filter')][ValidateSet('AND', 'OR')][string]$FilterCondition = 'AND'
        , [Parameter(Mandatory = $true, ParameterSetName = 'FilterString')][string]$FilterString
        , [alias('RowCount')][int]$Limit
        , [Parameter(Mandatory = $false)][switch]$PassThru
        , [System.Management.Automation.PSCredential]$Credential
        , [int]$CommandTimeout
        , [switch]$OnlyShowQuery
    )

    $PSBoundParameters.Keys | ? { $PassThruSqlcmdParam.Contains($_) } | % -Begin { $SqlQueryParam = @{} } -Process { $SqlQueryParam[$_] = $PSBoundParameters[$_] }

    if ($Limit) {
        $RowCount = "SET ROWCOUNT $Limit;"
    }
    else {
        $RowCount = ''
    }

    $OUTPUT = @{$true = 'OUTPUT DELETED.*'; $false = '' }[$PSBoundParameters.ContainsKey('PassThru')]

    if ($Filter) {
        $iFilter = Import_Data -HashTable $Filter
        $FilterKeys = $iFilter.Keys
        $FilterValues = $iFilter.Values

        [string[]]$wheres = @()
        for ($i = 0; $i -lt $FilterKeys.Count; $i++) {
            $key = $FilterKeys[$i]
            $value = $FilterValues[$i].Replace('*', '%')
            $operator = if ($value -like '*%*') { 'like' } else { '=' }
            $wheres += "[$key] $operator $value"
        }

        $where = $wheres -join " $FilterCondition "
    }
    else {
        $where = $FilterString
    }

    Invoke-SqlCommand @SqlQueryParam -Query "$RowCount DELETE $Table $OUTPUT WHERE $where"

}

Function Get-SqlRecord {
    <#
.EXAMPLE
Get-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Filter @{Name = 'win*'}
 
Name ServiceType Status
---- ----------- ------
WinHttpAutoProxySvc Win32OwnProcess, Win32ShareProcess Running
WinDefend Win32OwnProcess Running
Winmgmt Win32OwnProcess Running
WinRM Win32OwnProcess, Win32ShareProcess Running
.EXAMPLE
Get-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Filter @{Name = 'win*'} -SortBy ServiceType -SortDirection ASC -Property Name,ServiceType,Status -RowNumberSortBy ServiceType -RowNumberPartition ServiceType
 
RowNumber Name ServiceType Status
--------- ---- ----------- ------
        1 WinDefend Win32OwnProcess Running
        2 Winmgmt Win32OwnProcess Running
        1 WinRM Win32OwnProcess, Win32ShareProcess Running
        2 WinHttpAutoProxySvc Win32OwnProcess, Win32ShareProcess Running
.EXAMPLE
Get-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Filter @{Name = 'win*'} -Property Status,'COUNT(*) AS Counter' -GroupBy Status
 
Status Counter
------ -------
Running 4
.EXAMPLE
Get-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Property Status,'COUNT(*) AS Counter' -GroupBy Status
 
Status Counter
------ -------
Running 190
Stopped 167
.EXAMPLE
Get-SqlRecord -Server sql -SqlDatabase test1 -SqlTable ps -Property Status,'COUNT(*) AS Counter' -GroupBy Status -OnlyShowQuery
 SELECT Status,COUNT(*) AS Counter FROM ps GROUP BY Status
#>

    [CmdletBinding(DefaultParameterSetName = 'Filter')]
    param (
        [Parameter(Mandatory = $true)][Alias('ServerInstance', 'SqlServer')][String]$Server
        , [Parameter(Mandatory = $true)][Alias('SqlDatabase')][String]$Database
        , [Parameter(Mandatory = $true)][Alias('SqlTable')][String]$Table
        , [string[]]$Property = '*'
        , [alias('Count')][int]$Top
        , [alias('RowCount')][int]$Limit
        , [string]$SortBy
        , [string][ValidateSet('ASC', 'DESC')]$SortDirection = 'ASC'
        , [Parameter(ParameterSetName = 'Filter')][Hashtable]$Filter
        , [Parameter(ParameterSetName = 'Filter')][ValidateSet('AND', 'OR')][string]$FilterCondition = 'AND'
        , [Parameter(ParameterSetName = 'FilterString')][string]$FilterString
        , [System.Management.Automation.PSCredential]$Credential
        , [string[]]$RowNumberSortBy
        , [string][ValidateSet('ASC', 'DESC')]$RowNumberSortDirection = 'ASC'
        , [string[]]$RowNumberPartition
        , [string[]]$GroupBy
        , [string]$Having
        , [switch]$OnlyShowQuery
        , [int]$CommandTimeout
    )

    $PSBoundParameters.Keys | ? { $PassThruSqlcmdParam.Contains($_) } | % -Begin { $SqlQueryParam = @{} } -Process { $SqlQueryParam[$_] = $PSBoundParameters[$_] }

    if ($RowNumberSortBy) {
        $RowNumberSortBy = 'ORDER BY ' + ($RowNumberSortBy -join ',') + " $RowNumberSortDirection"
        if ($RowNumberPartition) {
            $RowNumberPartitions = 'PARTITION BY ' + ($RowNumberPartition -join ',')
        }
        $RowNumber = " ROW_NUMBER() OVER($RowNumberPartitions $RowNumberSortBy $RowNumberSortDesc) AS [RowNumber], "
    }

    if ($Top) {
        $Count = "TOP $Top"
    }
    else {
        $Count = ''
    }

    if ($Limit) {
        $RowCount = "SET ROWCOUNT $Limit;"
    }
    else {
        $RowCount = ''
    }

    $Properties = $Property -join ','

    if ($SortBy) {
        $SortString = " ORDER BY $SortBy $SortDirection"
    }
    else {
        $SortString = ''
    }

    if ($GroupBy) {
        $GroupBy = 'GROUP BY ' + ($GroupBy -join ',')
    }

    if ($GroupBy -and $Having) {
        $Having = "HAVING $Having"
    }
    else {
        $Having = ''
    }

    if ($Filter) {
        $iFilter = Import_Data -HashTable $Filter
        $FilterKeys = $iFilter.Keys
        $FilterValues = $iFilter.Values

        [string[]]$wheres = @()
        for ($i = 0; $i -lt $FilterKeys.Count; $i++) {
            $key = $FilterKeys[$i]
            $value = $FilterValues[$i].Replace('*', '%')
            $operator = if ($value -like '*%*') { 'like' } else { '=' }
            $wheres += "[$key] $operator $value"
        }

        $where = 'WHERE ' + ($wheres -join " $FilterCondition ")
    }
    elseif ($FilterString) {
        $where = 'WHERE ' + $FilterString
    }
    else {
        $where = ''
    }

    Invoke-SqlCommand @SqlQueryParam -Query "$RowCount SELECT $Count $RowNumber $Properties FROM $Table $where $SortString $GroupBy $Having"

}

Filter ConvertTo_HashTable {
    param(
        [System.Array]$Property = @()
        , [System.Array]$ExcludeProperty = @()
        , [switch]$SkipNullOrEmpty
        , [switch]$SkipNullOrWhiteSpace
    )
    Begin {
        $paramSelect = @{'ErrorAction' = 'SilentlyContinue' }
        if ($Property) { $paramSelect['Property'] = $Property }
        if ($ExcludeProperty) { $paramSelect['ExcludeProperty'] = $ExcludeProperty }
        $Skip = ($SkipNullOrEmpty -or $SkipNullOrWhiteSpace)
    }
    Process {
        $Hash = @{}
        if (!$Skip) {
            $_ | Select-Object @paramSelect | % { $_.psobject.Properties } | % {
                $Hash[$_.Name] = $_.Value
            }
        }
        else {
            $_ | Select-Object @paramSelect | % { $_.psobject.Properties } | % {
                if (!(($SkipNullOrEmpty -and [string]::IsNullOrEmpty($_.Value)) -or ($SkipNullOrWhiteSpace -and [string]::IsNullOrWhiteSpace($_.Value)))) {
                    $Hash[$_.Name] = $_.Value
                }
            }
        }
        $Hash
    }
    End {}
}

Function Import_Data {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({ @($_.psobject.Properties).Count })]
        [Alias('Data')]
        [HashTable[]]$HashTable
        , [double]$StringReserveMultiple = 1.5
        , [int]$TruncateString = 4000
        , [switch]$CreateTable
    )

    Begin {
        $Types = @{}

        $TypeMap = @{
            'System.Int32'   = 'int'
            'System.Int64'   = 'bigint'
            'System.Single'  = 'float'
            'System.Double'  = 'float'
            'System.Decimal' = 'float'
        }

        Filter ConvertTo-Json_ {
            param(
                [switch]$Compress
            )
            Begin { $Strings = New-Object -TypeName System.Collections.ArrayList }
            Process {
                $String = if ($_.psobject.TypeNames -like 'System.Collections.*') {
                    $_.GetEnumerator() | % -Begin { $Hash = @{} } -Process { $Hash[$_.Name.ToString()] = $_.Value } -End { ConvertTo-Json @PSBoundParameters -InputObject $Hash }
                }
                elseif ($_.psobject.TypeNames -like 'System.DateTime') {
                    ConvertTo-Json @PSBoundParameters -InputObject $_.ToString('yyyy-MM-ddTHH:mm:ss.fff')
                }
                elseif (!($_.psobject.TypeNames -like 'System.ValueType') -and !($_.psobject.TypeNames -like 'System.String')) {
                    $_.psobject.Properties | % -Begin { $Hash = @{} } -Process { $Hash[$_.Name.ToString()] = $_.Value } -End { ConvertTo-Json @PSBoundParameters -InputObject $Hash }
                }
                else {
                    ConvertTo-Json @PSBoundParameters -InputObject $_.ToString()
                }
                [void]$Strings.Add($String)
            }
            End {
                if ($Strings.Count -ge 1) {
                    '[' + ($Strings -join ',') + ']'
                }
                else {
                    $Strings
                }
            }
        }

    }

    Process {
        $HashTable | % {
            $_HashTable = $_
            $Keys = $_HashTable.Keys
            $Values = $Keys | % {
                $Key = $_
                $Value = $_HashTable[$Key]
                [bool]$quote = $false

                try {
                    $Type = $Value.GetType()
                }
                catch {
                    $Type = $null
                }

                if (!$Type -or $null -eq $Value -or $Value.GetType() -eq [System.DBNull]) {
                    $_Value = 'NULL'
                }
                elseif ($Value.GetType() -in @([int], [int64])) {
                    $_Value = $Value
                }
                elseif ($Value.GetType() -in @([Float], [Double], [Decimal])) {
                    [string]$_Value = $Value.ToString().Replace(',', '.')
                }
                elseif ($Value -is [bool]) {
                    $quote = $true
                    [string]$_Value = $Value
                }
                elseif ($Value -is [DateTime]) {
                    $quote = $true
                    [string]$_Value = $Value.ToString('yyyy-MM-ddTHH:mm:ss.fff')
                }
                elseif ($Value.psobject.TypeNames -like '*System.Collections*' -or $Value.psobject.TypeNames -like '*System.Array*') {
                    $quote = $true
                    [string]$_Value = $Value | ConvertTo-Json_ -Compress
                }
                elseif (!($Value.psobject.TypeNames -like 'System.ValueType') -and !($Value.psobject.TypeNames -like 'System.String')) {
                    $quote = $true
                    [string]$_Value = $Value | ConvertTo-Json_ -Compress
                }
                else {
                    $quote = $true
                    [string]$_Value = $Value
                }

                if ($quote) {
                    $_Value = $_Value.Replace($q, $qq)
                }

                if ($_Value.Length -gt $TruncateString) {
                    $_Value = -join ($_Value)[0..($TruncateString - 1)]
                }

                if ($CreateTable) {
                    if (!$Types.ContainsKey($Key)) {
                        $Types[$Key] = @{
                            Type      = $null
                            Length    = 0
                            AllowNull = $false
                            MaxValue  = 0
                        }
                    }

                    if (!$Type -or $null -eq $Value -or $Value.GetType() -eq [System.DBNull]) {
                        $Types[$Key]['AllowNull'] = $true
                    }
                    else {
                        if ($Type -in @([int], [int64], [Float], [Double], [Decimal])) {
                            if (!$Types[$Key]['Type'] -or !$Types[$Key]['MaxValue']) {
                                $Types[$Key]['MaxValue'] = [System.Int32]::MaxValue
                                $Types[$Key]['Type'] = $TypeMap[$Type.FullName]
                            }
                            if ($Type::MaxValue -gt $Types[$Key]['MaxValue']) {
                                $Types[$Key]['MaxValue'] = $Type::MaxValue
                                $Types[$Key]['Type'] = $TypeMap[$Type.FullName]
                            }
                        }
                        elseif ($Value -is [DateTime]) {
                            if (!$Types[$Key]['Type']) {
                                $Types[$Key]['Type'] = 'date'
                            }
                            if ($Value.TimeOfDay.TotalSeconds -ne 0 -and $Types[$Key]['Type'] -eq 'date') {
                                $Types[$Key]['Type'] = 'datetime'
                            }
                        }
                        elseif ($Value.psobject.TypeNames -like '*System.Collections*' -or $Value.psobject.TypeNames -like '*System.Array*') {
                            $Types[$Key]['Length'] = 4000
                            $Types[$Key]['Type'] = 'nvarchar(MAX)'
                        }
                        else {
                            $TrimLength = $_Value.Trim($q).Length

                            [int]$Length = $TrimLength * $StringReserveMultiple

                            if ($Length -le 4000) {
                                if ($TrimLength -gt $Types[$Key]['Length']) {
                                    $Types[$Key]['Length'] = $Length
                                    $Types[$Key]['Type'] = "nvarchar($Length)"
                                }
                            }
                            else {
                                $Types[$Key]['Length'] = 4000
                                $Types[$Key]['Type'] = 'nvarchar(MAX)'
                            }
                        }


                    }

                    $Types.GetEnumerator() | % {
                        if (!$Types[$_.Name]['Type']) {
                            $Types[$_.Name]['Type'] = 4000
                            $Types[$_.Name]['Type'] = 'nvarchar(MAX)'
                        }
                    }
                }

                if ($quote) {
                    $_Value = $q + $_Value + $q
                }

                $_Value
            }
        }
    }

    End {
        [PSCustomObject]@{
            Keys   = [string[]]$Keys
            Values = [string[]]$Values
            Types  = $Types
        }
    }
}

Export-ModuleMember -Alias @(
    , 'New-SqlRecord'
    , 'Insert-SqlRecord'
    , 'Update-SqlRecord'
    , 'Set-SqlRecord'
    , 'Create-SqlTable'
    , 'New-SqlTable'
)