Public/Redis.ps1

<#
.SYNOPSIS
Initializes the connection with the redis server
 
.DESCRIPTION
Initializes the connection with the redis server
 
.Parameter ConnectionString
The connection string to connect to the redis server. Example: "redisUrl.com:6380,password=PaSSwOrd,ssl=True,abortConnect=False"
 
.EXAMPLE
Connect-Redis -ConnectionString "redisUrl.com:6380,password=PaSSwOrd,ssl=True,abortConnect=False"
#>

function Connect-Redis
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $ConnectionString
    )

    # first, disconnect any existing connection
    Disconnect-Redis

    # open a new connection
    if (!(Test-RedisIsConnected $Global:PsRedisCacheConnection))
    {
        if ([string]::IsNullOrWhiteSpace($ConnectionString)) {
            throw 'No connection string supplied when creating connection to Redis'
        }

        $Global:PsRedisServerConnection = $null
        $Global:PsRedisCacheConnection = [StackExchange.Redis.ConnectionMultiplexer]::Connect($ConnectionString, $null)
        if (!$?) {
            throw 'Failed to create connection to Redis'
        }
    }

    # set the redis server
    $server = $Global:PsRedisCacheConnection.GetEndPoints()[0]

    if (!(Test-RedisIsConnected $Global:PsRedisServerConnection))
    {
        $Global:PsRedisServerConnection = $Global:PsRedisCacheConnection.GetServer($server)
        if (!$?) {
            throw "Failed to open connection to server"
        }
    }
}

<#
.SYNOPSIS
Closes the connection with the redis server
 
.DESCRIPTION
Closes the connection with the redis server
 
.EXAMPLE
Disconnect-Redis
#>

function Disconnect-Redis
{
    [CmdletBinding()]
    param()

    if (Test-RedisIsConnected $Global:PsRedisCacheConnection)
    {
        $Global:PsRedisCacheConnection.Dispose()
        if (!$?) {
            throw "Failed to dispose Redis connection"
        }

        $Global:PsRedisCacheConnection = $null
    }
}

<#
.SYNOPSIS
Connects to Redis, invokes a script, and then disconencts the session.
 
.DESCRIPTION
Connects to Redis, invokes a script, and then disconencts the session.
 
.PARAMETER ConnectionString
The connection string to connect to the redis server. Example: "redisUrl.com:6380,password=PaSSwOrd,ssl=True,abortConnect=False"
 
.PARAMETER ScriptBlock
The ScriptBlock to be invoked with other PsRedis functions.
 
.PARAMETER Arguments
Any options Arguments to supply tot eh ScriptBlock
 
.EXAMPLE
Invoke-RedisScript -ConnectionString 'redisUrl.com:6380' -ScriptBlock { Get-RedisInfo }
#>

function Invoke-RedisScript
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $ConnectionString,

        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $Arguments
    )

    # connect to redis
    Connect-Redis -ConnectionString $ConnectionString

    try {
        # run the script
        if (($null -eq $Arguments) -or ($Arguments.Length -eq 0)) {
            . $ScriptBlock
        }
        else {
            . $ScriptBlock @Arguments
        }
    }
    finally {
        # disconnect from redis
        Disconnect-Redis
    }
}

<#
.SYNOPSIS
Gets the keys section of the redis info command
 
.DESCRIPTION
Gets the keys section of the redis info command
 
.EXAMPLE
Get-RedisInfoKeys
#>

function Get-RedisInfoKeys
{
    [CmdletBinding()]
    param()

    $conn = Get-RedisConnection
    $k = 0

    if (($conn.Info() | Select-Object -Last 1)[0].Value -imatch 'keys=(\d+)') {
        $k = $Matches[1]
    }

    return $k
}

<#
.SYNOPSIS
Gets the results of the redis info command
 
.DESCRIPTION
Gets the results of the redis info command
 
.EXAMPLE
Get-RedisInfo
#>

function Get-RedisInfo
{
    [CmdletBinding()]
    param()

    $conn = Get-RedisConnection
    $info = $conn.Info()

    return $info
}

<#
.SYNOPSIS
Gets the uptime of the redis server
 
.DESCRIPTION
Gets the uptime of the redis server
 
.Parameter Granularity
Sets the granularity of the up time of the redis server. Can be either Seconds or Days
 
.EXAMPLE
Get-RedisUptime -Granularity 'Seconds'
#>

function Get-RedisUptime
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateSet('Seconds', 'Days')]
        [string]
        $Granularity
    )

    $info = Get-RedisInfo
    $key = "uptime_in_$($Granularity.ToLowerInvariant())"
    return ($info[0] | Where-Object { $_.Key -ieq $key } | Select-Object -ExpandProperty Value)
}

<#
.SYNOPSIS
Adds a new string redis key
 
.DESCRIPTION
Adds a new string redis key
 
.Parameter Key
The name of the key being added
 
.Parameter Value
The value of the key being added
 
.Parameter TTL
(Optional) When the key will expire. If not passed then a expire time will not be set
 
.EXAMPLE
Add-RedisKey -Key 'SessionGuid' -Value 'SessionData'
#>

function Add-RedisKey
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter()]
        [string]
        $Value,

        [Parameter()]
        [timespan]
        $TTL
    )

    $db = Get-RedisDatabase
    $value = $db.StringSet($Key, $Value, $TTL)

    return $value
}

<#
.SYNOPSIS
Removes all keys with a supplied pattern
 
.DESCRIPTION
Removes all keys with a supplied pattern
 
.Parameter Pattern
The pattern to match the keys to be removed. Example '*' will remove all keys, 'Session*' will remove all keys that start with 'Session'
 
.EXAMPLE
Remove-RedisKeys -Pattern 'Cheese*'
#>

function Remove-RedisKeys
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Pattern
    )

    $conn = Get-RedisConnection
    $count = 0

    foreach ($k in $conn.Keys($Global:PsRedisDatabaseIndex, $Pattern))
    {
        Remove-RedisKey -Key $k | Out-Null
        if (!$?) {
            throw "Failed to delete key: $($k)"
        }

        $count++
    }

    return $count
}

<#
.SYNOPSIS
Gets the count of all the keys with a supplied pattern
 
.DESCRIPTION
Gets the count of all the keys with a supplied pattern
 
.Parameter Pattern
The pattern to match the keys to be retrieved. Example '*' will retrieve all keys, 'Session*' will retrieve all keys that start with 'Session'
 
.EXAMPLE
Get-RedisKeysCount -Pattern 'Cheese*'
#>

function Get-RedisKeysCount
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Pattern = '*'
    )

    $conn = Get-RedisConnection

    if ([string]::IsNullOrWhiteSpace($Pattern)) {
        $Pattern = '*'
    }

    $keys = @{}

    foreach ($k in $conn.Keys($Global:PsRedisDatabaseIndex, $Pattern)) {
        $keys[($k -isplit ':')[0]]++
    }

    return $keys
}

<#
.SYNOPSIS
Gets the details of a redis key with the supplied key
 
.DESCRIPTION
Gets the details of a redis key with the supplied key
 
.Parameter Key
The key name of the key that will be retrieve
 
.Parameter Type
(Optional) The key type, helps to reduce the amount of round trips if already known
 
.EXAMPLE
Get-RedisKeyDetails -Key 'Grapes' -Type 'Set'
#>

function Get-RedisKeyDetails
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter()]
        [ValidateSet('hash', 'set', 'string')]
        [string]
        $Type
    )

    if ([string]::IsNullOrWhitespace($Type)){
        $Type = Get-RedisKeyType -Key $Key
    }

    $value = Get-RedisKey -Key $Key -Type $Type

    return @{
        Key = $Key
        Type = $Type
        Value = $value
        TTL = (Get-RedisKeyTTL -Key $Key).TotalSeconds
        Size = (Get-RedisKeyValueLengthPrivate -Data $value)
    }
}

<#
.SYNOPSIS
Gets the value of a redis key with the supplied key
 
.DESCRIPTION
Gets the value of a redis key with the supplied key
 
.Parameter Key
The key name of the key that will be retrieve
 
.Parameter Type
(Optional) The key type, helps to reduce the amount of round trips if already known
 
.EXAMPLE
Get-RedisKey -Key 'Grapes' -Type 'Set'
#>

function Get-RedisKey
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter()]
        [string]
        $Type
    )

    $db = Get-RedisDatabase

    if ([string]::IsNullOrWhitespace($Type)){
        $Type = Get-RedisKeyType -Key $Key
    }

    switch ($Type.ToLowerInvariant()) {
        'hash' {
            $value = [string]($db.HashGetAll($Key)).Value
        }

        'set' {
            $value = @([string]($db.SetMembers($Key)) -isplit '\s+')
        }

        default {
            $value = ($db.StringGet($Key)).ToString()
        }
    }

    return $value
}

<#
.SYNOPSIS
Gets the length of a redis key value.
If the key is of type set then it will return the amount of items in the set.
Otherwise, it will return the amount of characters in the value
 
.DESCRIPTION
Gets the length of a redis key value.
If the key is of type set then it will return the amount of items in the set.
Otherwise, it will return the amount of characters in the value
 
.Parameter Key
The key name of the key that will be retrieve for the length
 
.EXAMPLE
Get-RedisKeyValueLength -Key 'Grapes'
#>

function Get-RedisKeyValueLength
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key
    )

    return Get-RedisKeyValueLengthPrivate -Key $Key
}

<#
.SYNOPSIS
Gets a random key from the redis server
 
.DESCRIPTION
Gets a random key from the redis server
 
.EXAMPLE
Get-RedisRandomKey
#>

function Get-RedisRandomKey
{
    [CmdletBinding()]
    param()

    $db = Get-RedisDatabase
    $value = $db.KeyRandom()
    return $value
}

<#
.SYNOPSIS
Gets random keys from the redis server matching a supplied pattern
 
.DESCRIPTION
Gets random keys from the redis server matching a supplied pattern
 
.Parameter Pattern
The pattern then the key name needs to match
 
.Parameter ScriptBlock
A script block that will be ran for each key that matches the pattern.
Return a value of $false to make the key not count to the total
 
.Parameter KeyCount
The amount of keys to retrieve
 
.EXAMPLE
Get-RedisRandomKeys -Pattern 'Toaster*' -ScriptBlock {return $true} -KeyCount 10
#>

function Get-RedisRandomKeys
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Pattern = '',

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory=$true)]
        [int]
        $KeyCount = 1
    )

    $keys = @()

    if ($KeyCount -le 1){
        $KeyCount = 1
    }

    while ($keys.Length -lt $KeyCount){
        $key = [string](Get-RedisRandomKey)

        if (!([string]::IsNullOrWhiteSpace($Pattern)) -and !($key -imatch $Pattern)) {
            continue
        }

        $result = $true
        if ($null -ne $ScriptBlock) {
            $result = (Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $key)
        }

        if ($result) {
            $keys += $k
        }

        $i = ($keys.Length / $KeyCount) * 100
        Write-Progress -Activity "Search in Progress" -Status "$i% Complete:" -PercentComplete $i
    }

    return $keys
}

function Get-RedisRandomKeysQuick
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Pattern = '*',

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory=$true)]
        [int]
        $KeyCount = 1,

        [Parameter()]
        [int]
        $KeyOffset = 0,

        [Parameter()]
        [int]
        $PageSize = 10
    )

    $keys = @()
    $progress = 0

    if ($KeyCount -le 1) {
        $KeyCount = 1
    }

    while ($keys.Length -lt $KeyCount) {
        $keys += (Get-RedisKeys -Pattern $Pattern -KeyCount ($KeyCount - $keys.Length) -KeyOffset $KeyOffset -PageSize $PageSize -ScriptBlock {
            param($key)
            $allowed = ((Get-Random -Minimum 1 -Maximum 5) -eq 2)

            if ($allowed) {
                if ($null -ne $script:ScriptBlock) {
                    $allowed = (Invoke-Command -ScriptBlock $script:ScriptBlock -ArgumentList $key)
                }

                if ($allowed) {
                    $script:progress++
                    $i = ($script:progress / $script:KeyCount) * 100
                    Write-Progress -Activity "Search in Progress" -Status "$i% Complete:" -PercentComplete $i
                }
            }

            return $allowed
        })
    }

    return $keys
}

<#
.SYNOPSIS
Get a key's type from Redis.
 
.DESCRIPTION
Get a key's type from Redis.
 
.PARAMETER Key
The Key name to lookup.
 
.EXAMPLE
Get-RedisKeyType -Key 'UserId:123'
#>

function Get-RedisKeyType
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key
    )

    $db = Get-RedisDatabase
    $value = $db.KeyType($key)
    return $value.ToString()
}

<#
.SYNOPSIS
Get a key's TTL from Redis.
 
.DESCRIPTION
Get a key's TTL from Redis.
 
.PARAMETER Key
The Key name to lookup.
 
.EXAMPLE
Get-RedisKeyTTL -Key 'UserId:123'
#>

function Get-RedisKeyTTL
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key
    )

    $db = Get-RedisDatabase
    $value = $db.KeyTimeToLive($Key)
    return $value
}

<#
.SYNOPSIS
Set a key's TTL in Redis.
 
.DESCRIPTION
Set a key's TTL in Redis.
 
.PARAMETER Key
The Key to update.
 
.PARAMETER TTL
The TTL, in seconds.
 
.EXAMPLE
Set-RedisKeyTTL -Key 'UserId:123' -TTL 3600
#>

function Set-RedisKeyTTL
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [int]
        $TTL
    )

    $db = Get-RedisDatabase
    $db.KeyExpire($Key, [TimeSpan]::FromSeconds($TTL)) | Out-Null
}

function Get-RedisKeys
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Pattern = '*',

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [int]
        $KeyCount = 0,

        [Parameter()]
        [int]
        $KeyOffset = 0,

        [Parameter()]
        [int]
        $PageSize = 10
    )

    $conn = Get-RedisConnection
    $keys = @()

    if ([string]::IsNullOrWhiteSpace($Pattern)) {
        $Pattern = '*'
    }

    if ($KeyOffset -lt 0) {
        $KeyOffset = 0
    }

    if ($PageSize -lt 0) {
        $PageSize = 10
    }

    foreach ($k in $conn.Keys($Global:PsRedisDatabaseIndex, $Pattern, $PageSize, 0, $KeyOffset)) {
        $result = $true
        if ($null -ne $ScriptBlock) {
            $result = (Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $k)
        }

        if ($result) {
            $keys += $k
        }

        if (($KeyCount -gt 0) -and ($keys.Length -ge $KeyCount)) {
            break
        }
    }

    return $keys
}

function Remove-RedisKey
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key
    )

    $db = Get-RedisDatabase
    $db.KeyDelete($Key) | Out-Null
}

function Remove-RedisSetMember
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Member
    )

    $db = Get-RedisDatabase
    $db.SetRemove($Key, $Member) | Out-Null
}

function Add-RedisSetMember
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Member
    )

    $db = Get-RedisDatabase
    $db.SetAdd($Key, $Member) | Out-Null
}

function Set-RedisIncrementKey
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter()]
        [int]
        $Increment = 1
    )

    $db = Get-RedisDatabase
    $value = $db.StringIncrement($Key, $Increment) | Out-Null
    return $value
}

function Test-RedisTiming
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Key,

        [Parameter()]
        [int]
        $Seconds = 120,

        [switch]
        $NoSleep
    )

    if ($Seconds -le 0) {
        $Seconds = 1
    }

    $startTime = [DateTime]::UtcNow
    $times = @()

    # run and get duration for each call
    while ([DateTime]::UtcNow.Subtract($startTime).TotalSeconds -le $Seconds)
    {
        $_start = [DateTime]::UtcNow

        Set-RedisIncrementKey -Key $Key -Increment 1 | Out-Null

        $duration = [DateTime]::UtcNow.Subtract($_start).TotalMilliseconds
        $times += $duration

        if (!$NoSleep -and $duration -lt 1000) {
            Start-Sleep -Milliseconds (1000 - $duration)
        }
    }

    # remove the key
    Remove-RedisKey -Key $Key

    # loop through the duration, getting the average/min and max times
    $results = ($times | Measure-Object -Average -Minimum -Maximum)

    return @{
        Average = $results.Average
        Minimum = $results.Minimum
        Maximum = $results.Maximum
    }
}