Private/Powershell/PsDbModel.ps1

<############################################################################
 # Given a connection string to a SQL Server database,
 # check the metadata, and for each table in the database,
 # fill a custom TableInfo object with metadata about those tables and columns.
 #
 # This is not specific to C# or angular, it's just TypeScript objects
 # storing metadata about database tables.
 ############################################################################>

Function New-PsModel([DbInfo]$dbInfo)
{
    Write-Host "### Store metadata of database tables"
    $tableInfos = @{};

    $conn = Get-DbConnection $dbInfo.connStr
    $ds = Invoke-DbQuery $conn "select
            TABLE_NAME,
            iif(TABLE_TYPE = 'BASE TABLE', 0, 1) as IS_VIEW
        from
            INFORMATION_SCHEMA.TABLES
        where
            TABLE_TYPE in ('BASE TABLE', 'VIEW') "
 @{}

    $dt = $ds.Tables[0]

    foreach ($Row in $dt)
    { 
        [string]$tableName = $Row.TABLE_NAME
        [bool]$isView = $Row.IS_VIEW
        [TableInfo]$tableInfo = New-PsTable $dbInfo $tableName $isView
        $tableInfos.Add($tableName, $tableInfo)

    }
    Close-DbConnection $conn

    return $tableInfos
}

<############################################################################
 # Given a tablename and a connection string to a database,
 # make a POTO class where the POTO properties match metadata
 #
 # This is not specific to C# or angular, it's just TypeScript objects
 # storing metadata.
 ############################################################################>

Function New-PsTable([DbInfo]$dbInfo, [string]$tableName, [bool]$isView) 
{
    Write-Host "### Generate internal model for $($tableName)"
    
    $conn = Get-DbConnection $dbInfo.connStr
    # Get a list of all columns in this table
    $ds = Invoke-DbQuery $conn "select
                C.COLUMN_NAME,
                C.IS_NULLABLE,
                C.DATA_TYPE,
                CHARACTER_MAXIMUM_LENGTH,
                iif(
                    (
                        SELECT
                            ku.COLUMN_NAME
                        FROM
                            INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
                            inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS ku ON tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
                                AND tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
                        where
                            c.TABLE_CATALOG = tc.TABLE_CATALOG
                            AND c.TABLE_SCHEMA = tc.TABLE_SCHEMA
                            AND c.TABLE_NAME = tc.TABLE_NAME
                            AND c.COLUMN_NAME = ku.COLUMN_NAME
                    ) is null, 'NO', 'YES') as PRIMARY_KEY
            from
                INFORMATION_SCHEMA.COLUMNS as C
            where
                TABLE_NAME = @TableName"
 @{ TableName = $tableName }
    $dt = $ds.Tables[0]

    # Generate a TypeScript POTO file that represents this table

    [ColumnInfo[]] $columnInfos = @();
        
    # Loop through all fields
    foreach ($Row in $dt)
    { 
        [string]$fieldName = $row.COLUMN_NAME
        [string]$isNullable = $row.IS_NULLABLE
        [string]$dataType = $row.DATA_TYPE
        [int]$charMaxLen = $null;
        if($row.CHARACTER_MAXIMUM_LENGTH -match '^\d+$') {
            $charMaxLen = $row.CHARACTER_MAXIMUM_LENGTH
        }
        [string]$isPrimaryKey = $row.PRIMARY_KEY

        [ColumnInfo] $columnInfo = [ColumnInfo]::new($fieldName, $dataType, ($isNullable -eq "yes"), $charMaxLen, ($isPrimaryKey -eq "yes"))
        $columnInfos = $columnInfos += $columnInfo
    }
    Close-DbConnection $conn

    [TableInfo] $tableInfo = [TableInfo]::new($tableName, $columnInfos, $isView)
    return $tableInfo
}