PrivateFunctions/Classes/CacheService.ps1

Class CacheSvc {
    hidden $Cache = @{}
    
    [void] Add([string]$key, $value) { $this.Add($key, $value, 300) }
    [void] Add([string]$key, $value, [int]$age) {
        $this.Cache[$key] = @{
            Expires = (Get-Date).AddSeconds($age)
            Value   = $value
        }
    }

    [bool] IsValid([string]$key) {
        return ($this.Cache.ContainsKey($key) -and $this.Cache[$key].Expires -gt (Get-Date))
    }

    [object] Get($key) {
        if ($this.IsValid($key)) { return $this.Cache[$key].Value }
        else { return $null }
    }
    [void] Expire([string]$key) { if($this.Cache.ContainsKey($key)) { $this.Cache.Remove($key) } }
    [void] Clear() { $this.Cache.Clear() }
    [string[]] Keys() { return $this.Cache.Keys }
}

Class SchemaCache {
    hidden [CacheSvc] $_tables = [CacheSvc]::new()
    hidden [CacheSvc] $_columns = [CacheSvc]::new()

    [void] Initialize() {
        $this._tables.Clear()
        $this._columns.Clear()

        Get-DataVerseTables -AllTables |
            Select-Object EntitySetName, LogicalName, PrimaryIdAttribute, PrimaryNameAttribute, IsManaged |
            ForEach-Object {
                $this._tables.Add($_.EntitySetName, $_)
                if (-not $_.IsManaged) {
                    $columns = Get-DataVerseColumns -EntitySetName $_.EntitySetName -Options All
                    $this._columns.Add($_.EntitySetName, $columns)
                }
            }
    }

    [psobject] Table([string]$entitySetName) {
        if (-not $this._tables.IsValid($entitySetName)) {
            $tbl = Get-DataVerseTables -EntitySetName $entitySetName
            $this._tables.Add($entitySetName, $tbl)
            return $tbl            
        }
        else {
            return $this._tables.Get($entitySetName)
        }
    }
    
    [string] LogicalName([string]$entitySetName) {
        return $this.Table($entitySetName).LogicalName
    }
    
    [string] TablePrimaryId([string]$entitySetName) {
        return $this.Table($entitySetName).PrimaryIdAttribute
    }

    [string[]] EntitySetNames() {
        return $this._tables.Keys()
    }

    [psobject[]] Columns([string]$entitySetName) {
        if (-not $this._columns.IsValid($entitySetName)) {
            $columns = Get-DataVerseColumns -EntitySetName $entitySetName -Options All
            $this._columns.Add($entitySetName, $columns)
            return $columns
        }
        else {
            return $this._columns.Get($entitySetName)
        }
    }
    [psobject[]] ColumnsCustom([string]$entitySetName) {
        return $this.Columns($entitySetName).Where({ $_.IsCustomAttribute -or $_.IsPrimaryId })
    }
    [psobject[]] ColumnsCanUpdate([string]$entitySetName) {
        return $this.Columns($entitySetName).Where({ $_.IsValidForUpdate -or $_.IsPrimaryId })
    }
    
}

Class SDVApp {
    static [CacheSvc] $Cache = [CacheSvc]::new()
    static [SchemaCache] $Schema = [SchemaCache]::new()

    static [void] InitializeSchema() { [SDVApp]::Schema.Initialize() }
    
    static [hashtable] GetBaseHeaders() { return [SDVApp]::GetBaseHeaders([SDVApp]::GetToken()) }
    static [hashtable] GetBaseHeaders([string]$token) {
        return @{
            'Authorization'    = 'Bearer ' + $token
            'Accept'           = 'application/json'
            'OData-MaxVersion' = '4.0'
            'OData-Version'    = '4.0'
        }
    }     

    static [string] GetToken() {
        if (-not [SDVApp]::_environmentUri) {
            throw [System.InvalidOperationException]::new("Cannot call 'GetToken()' unless 'SetEnvironment()' has been called first!")
        }
        return [SDVApp]::GetToken([SDVApp]::_environmentUri)
    }

    static [string] GetToken([string]$uri) {
        if (-not [SDVApp]::Cache.IsValid("IsAuthenticated")) {
            ## Login interactively if not already logged in
            if ($null -eq (Get-AzTenant -ErrorAction SilentlyContinue)) {
                Connect-AzAccount -ErrorAction Stop | Out-Null
            }
            [SDVApp]::Cache.Add("IsAuthenticated", $true, 15 * 60)
        }
        
        $key = "[TOKEN]$uri"
        $token = [SDVApp]::Cache.Get($key)
        if (-not $token) {
            $secureToken = (Get-AzAccessToken -ResourceUrl $uri -AsSecureString).Token

            # Convert the secure token to a string
            $token = [System.Net.NetworkCredential]::new("", $secureToken).Password
            [SDVApp]::Cache.Add($key, $token)
        }
        return $token
    }

    static [psobject[]] DataVerseEnvironments() { return [SDVApp]::DataVerseEnvironments($false) }
    static [psobject[]] DataVerseEnvironments([bool]$force) {
        $result = [SDVApp]::Cache.Get("DataVerseEnvironments")
        if ($force -or -not $result) {
            $request = @{
                Uri     = 'https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances?$select=ApiUrl,FriendlyName'
                Method  = "GET"
                Headers = [SDVApp]::GetBaseHeaders([SDVApp]::GetToken("https://globaldisco.crm.dynamics.com/"))
            }
            $response = Invoke-RestMethod @request
            $result = $response.value
            [SDVApp]::Cache.Add("DataVerseEnvironments", $result)
        }
        return $result        
    }

    #region "Environment/Baseuri/Headers/Token"
    hidden static [string]$_environmentUri
    hidden static [string]$_baseUri
    static [void] SetEnvironment([string]$uri) {
        if (-not $uri.EndsWith("/")) { $uri += "/" }
        [SDVApp]::_environmentUri = $uri
        [SDVApp]::_baseUri = $uri + 'api/data/v9.2/'
    }
    static [string] GetBaseUri() { return [SDVApp]::_baseUri }
    #endregion
}

#region Old
<#
$Script:Cache = @{
 
}
Function CacheAdd {
    param([Parameter(Mandatory, Position=0)][string]$Key
        , [Parameter(Mandatory, position=1)]$Value
        , [Parameter()][int]$AgeInSeconds = 300)
 
    $Script:Cache[$Key] = @{
        Expires = (Get-Date).AddSeconds($AgeInSeconds)
        Value = $Value
    }
}
 
Function CacheValid([Parameter(Mandatory)][string]$Key) {
    return ($Script:Cache.ContainsKey($Key) -and $Script:Cache[$Key].Expires -gt (Get-Date))
}
 
Function CacheGet {
    param([Parameter(Mandatory)][string]$Key)
    if(CacheValid -Key $Key) {
        return $Script:Cache[$key].Value
    }
}
 
Function CacheClear { $Script:Cache.Clear() }
#>

#endregion