write-crud.ps1

function Write-CRUD
{
    <#
    .Synopsis
        Writes CRUD commands for a table in Azure
    .Description
        Writes PowerShell commands to create a CRUD system around a table and partition in Azure
         
         
        CRUD stands for Create, Read, Update, and Delete
         
         
        Write-Crud creates the following commands.
         
        * Add-$Noun (aliased to New-$Noun and Create-$Noun)
        * Get-$Noun (aliased to Search-$Noun and Read-$Noun)
        * Update-$noun (aliased to Set-$Noun)
        * Remove-$Noun (aliased to Remove-$Noun)
     
         
        Write-Crud can create tables with an arbitrary schema.
         
         
        It can also use a well-known schemas, found at either [http://schema.org](http://schema.org) or [http://shouldbeonschema.org](http://shouldbeonschema.org)
         
    .Example
        Write-CRUD -Table My -Partition CustomItem -TypeName MyCustomCrud -Field @{
            'Name' = 'The Name of the Item'
            'Description' = 'The description of the item'
        }
    .Example
        Write-Crud -Table My -Partition Blog -Schema http://schema.org/BlogPosting
    .Link
        Add-AzureTable
    .Link
        Get-AzureTable
    .Link
        Search-AzureTable
    .Link
        Set-AzureTable
    .Link
        Update-AzureTable
    .Link
        Remove-AzureTable
    .Link
        Using_Azure_Table_Storage_In_Pipeworks
    .Link
        Writing_Crud_In_Pipeworks
    .Link
        http://schema.org
    .Link
        http://shouldbeonschema.org
    #>

    [CmdletBinding(DefaultParameterSetName='Schema')]
    [OutputType([string])]
    param(
    # The name of the table
    [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)]
    [string]$Table,
    
    # The name of the partition
    [Parameter(Mandatory=$true,Position=1,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)]
    [string]$Partition,
    
    # The storage account setting
    [string]$StorageAccountSetting = 'AzureStorageAccountName',
    # The storage key setting
    [string]$StorageKeySetting = 'AzureStorageAccountKey',

    # The SQL Connection String Setting
    [string]$ConnectionStringSetting = 'SqlAzureConnectionString',
    
    # Any arbitrary fields to put into a custom CRUD system.
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='CustomField',Position=3)]
    [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName='Schema')]
    [Hashtable]$Field,
    
    # The typename of the data in the field
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='CustomField',Position=4)]
    [string]$TypeName,
    
    # The Schema.org schema used for the table
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='Schema',Position=2)]
    [string]$Schema,
    
    # If set, will require certain fields
    [string[]]$RequiredField,
    
    # The parition where read codes would be found
    
    [string]$ReadCodePartition,
    
    # If set, the Read command in the CRUD system will automatically sort results
    [string]$SortField,
    
    # If set, the Read command in the CRUD system will automatically sort results as a type
    [Parameter(Position=6)]
    [ValidateSet('String','DateTime','Float','Int')]
    [string]$SortType,
    
    # The field on the object that references the read code object
    [string]$ReadCodeCrossReferenceField,
    
    # If set, will make these fields request multiple lines for input
    [string[]]$LargeField = 'description',

    # If set, will make these fields editable HTML
    [string[]]$HtmlField = @('description', 'articlebody'),
    
    # If set, will only include a few fields from the schema
    [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName='Schema')]
    [string[]]$IncludeField,
   
    
    # The noun to use for the generated command. If this is not set, the commands will have a noun named $Table$Partition
    [string]$Noun,

    # The verbs that will be generated. By default, Add, Get, Remove, and Update
    [ValidateSet("Add","Get","Remove","Update")]
    [string[]]$Verb = @("Add", "Get", "Remove", "Update"),
    
    # If set, the CRUD system will be designed to work with users, and be able to query for my items or items of a user ID
    [Switch]$IsUserSystem,
    
    # The partition to look in for user names and ids
    [string]$UserPartition,
    
    # The type of key used by the crud system
    [Parameter(Position=5)]
    [ValidateSet('Guid', 'Hex', 'SmallHex', 'Sequential', 'Named', 'Parameter')]   
    [string]$KeyType  = 'Hex',
        
    # The order of the fields
    [string[]]$FieldOrder,
    
    # If set, will not convert markdown found in the table
    [Switch]$DoNotConvertMarkdown,
    
    # If set, will not connect to storage
    [Switch]$DoNotConnect,

    # If set, will force uniquely named items
    [Switch]$UniquelyNamed,
    
    # A description to use for the commands
    [string]$Description       
    )
    
    process {
        $fieldParameterBlock = ""        
        # Create (Add-$Name, alias New-$Name, Create-$Name, Put-$Name)
        
                
        if ($psCmdlet.ParameterSetName -eq 'CustomField') {
                
            # If custom fields are provided, use them to set up the CRUD schema
            $schemaFields = 
                $Field.GetEnumerator() |
                    ForEach-Object {
                        New-Object PSOBject -Property @{
                            Name = $_.key
                            Description = $_.Value
                        }
                    }
                    
        } elseif ($psCmdlet.ParameterSetName -eq 'Schema') {
        
            # If not, grab the schema from the site
            $schemaHtml = $schema | 
                Get-Web -HideProgress -Url {$_ }
                
            # First, try parsing out the properties as microdata
            $schemaFields = 
                Get-Web -Html $schemaHtml -ItemType http://shouldbeonschema.org/Property
                
            
            # Add an ID to schemas from schema.org, if not present
            $hasId = $false
            
            
            if (-not $schemaFields) {            
                # Schema.org requires hand parsing of the HTML
                $schemaFields = Get-Web -Html $schemaHtml -Tag tr  | 
                    # Luckily, they use clean CSS. Don't ever change, schema.org (seriously, it will break me)
                    Where-Object {$_.Tag -like "*prop-nam*" }  | 
                    # Now pick out the key pieces of information from the schema: the name and the description
                    ForEach-Object {
                        # The name is in a code element
                        $name = $_.xml.th.code.innerText
                        # A string description would be the innerText of the last column in the table row
                        $description = @($_.xml.td)[-1].innerText                     
                        if ($name -eq 'Id') {
                            $hasId= $true
                        }
                        
                        if ($includeField -and $includeField -contains $name) {
                            New-Object PSOBject -Property @{
                                Name = $name
                                Description = $description
                            }
                        } else {
                            New-Object PSOBject -Property @{
                                Name = $name
                                Description = $description
                            }
                        }
                        
                    }  -End {
                        
                        if (-not $hasId) {
                            New-Object PSObject -Property @{
                                Name= 'Id'
                                Description = "An Identifier for the Object"
                            }
                        }
                    }
            }
            
            if ($field) {
                $schemaFields += $Field.GetEnumerator() |
                        ForEach-Object {
                            New-Object PSOBject -Property @{
                                Name = $_.key
                                Description = $_.Value
                            }
                        }
            }
        }  
        
        
        
        # If this came from schema.org, use that typename. Otherwise, use the typename provided
        $objTypeNAme = $(if ($psCmdlet.parameterSetName -eq 'schema') {$schema }else {$typeName })
        $p =1
        if (-not $RequiredField) {
            $RequiredField = 'Name'
        }
        
        # If there was a field order, use this to pick out the schema fields
        if ($fieldOrder) {
            $nameTable = $schemaFields | Where-Object { $_.Name } | Group-Object Name -AsHashTable
            $schemaFields = foreach ($f in $fieldOrder) {
                $nameTable.$f
            }
        }
                    
        foreach ($sc in @($schemaFields)) {
            if (-not $sc) { continue }
            
            # Add a mandatory attribute for required fields
            $mandatoryAttribute =
                if ($RequiredField -contains $sc.Name) {
                    'Mandatory=$true, '
                } else {
                    ''
                }
            
            # Force name to be position 0, if found
            if ($sc.Name -eq 'Name') {
                $oldP = $p
                $p =0                    
            }


            $InputLines = 1
            
            $ParameterTypeAttribute = '[string]'
            if ($sc.PropertyType -eq 'Boolean') {
                $ParameterTypeAttribute = '[switch]'
            } elseif ($sc.PropertyType -eq 'Integer') {
                $ParameterTypeAttribute = '[int]'
            } elseif ($sc.PropertyType -eq 'Float') {
                $ParameterTypeAttribute = '[Double]'
            } elseif ($sc.PropertyType -like "*date*") {
                $ParameterTypeAttribute = '[DateTime]'
            } elseif ($sc.Name -like "Date*") {
                $ParameterTypeAttribute = '[DateTime]'
            }
            if (-not $sc.Name) { continue }
            $capitalizedName = $sc.Name[0].ToString().ToUpper() + $sc.Name.Substring(1)
            $fieldParameterBlock+= "
    <#
    $($sc.Description)
    $(if ($largeField -contains $sc.Name) { '|LinesForInput 10' })
    $(if ($htmlField -contains $sc.Name) { '|ContentEditable' })
    #>
    [Parameter(${mandatoryAttribute}Position=$p, ValueFromPipelineByPropertyName=`$true)]
    $ParameterTypeAttribute
    "
 +'$' + $capitalizedName + ","
            # We really want name to be first
            if ($p -eq 0) {
                $p = $oldP
            }
            $p++
        }              
            
        # The CRUD system will keep items compressed, and, unless told otherwise, will automatically convert items kept in Markdown within the CRUD system into HTML
        $unpackParameterBlock = "`$DoNotConvertMarkdown = $(if ($DoNotConvertMarkdown) { '$true'} else {'$false'});
        "
 + {            
            $item = $_
            $item.psobject.properties |                         
                Where-Object { 
                    $_.Value -and
                    ('Timestamp', 'RowKey', 'TableName', 'PartitionKey' -notcontains $_.Name) -and
                    (-not $_.Value.ToString().Contains(' ')) 
                }|                        
                ForEach-Object {
                    try {
                        $expanded = Expand-Data -CompressedData $_.Value
                        if ($expanded) {
                            $item | Add-Member NoteProperty $_.Name $expanded -Force
                        }                        
                    } catch{
                        Write-Verbose $_
                    
                    }
                }
                
            if (-not $DoNotConvertMarkdown) {                 
                $item.psobject.properties |                         
                    Where-Object { 
                        $_.Value -and
                        ('Timestamp', 'RowKey', 'TableName', 'PartitionKey' -notcontains $_.Name) -and
                        (-not $_.Value.ToString().Contains('<')) 
                    }|                                   
                    ForEach-Object {
                        try {
                            $fromMarkdown = ConvertFrom-Markdown -Markdown $_.Value
                            $item | Add-Member NoteProperty $_.Name $fromMarkdown -Force
                        } catch{
                            Write-Verbose $_
                        
                        }
                    }
            }
            $item     
            
        }
       
        #$fieldParameterBlock
        $beginBlock = "
    begin {
        `$storageAccount = if ((Get-WebConfigurationSetting -Setting '$StorageAccountSetting')) {
            (Get-WebConfigurationSetting -Setting '$StorageAccountSetting')
        } elseif ((Get-SecureSetting -Name '$StorageAccountSetting' -ValueOnly)) {
            (Get-SecureSetting -Name '$StorageAccountSetting' -ValueOnly)
        }
 
        `$storageKey = if ((Get-WebConfigurationSetting -Setting '$StorageKeySetting')) {
            (Get-WebConfigurationSetting -Setting '$StorageKeySetting')
        } elseif ((Get-SecureSetting -Name '$StorageKeySetting' -ValueOnly)) {
            (Get-SecureSetting -Name '$StorageKeySetting' -ValueOnly)
        }
        "

            
        $joinWith = "`",$([Environment]::NewLine + ' ' * 12)`""
        if (-not $TypeName) {
            $schemaName = ([uri]$schema).Segments[-1]            
            $TypeName = $schemaName
        }
        if ($IsUserSystem) {
            $attachUserIdChunk = {
                $UserID = if ($session -and $session['User'].UserId) {
                    $session['User'].UserId
                } elseif ($Request -and $Request['AppKey']) {
                    $userFound = 
                        Search-AzureTable -StorageAccount $storageAccount -StorageKey $storageKey -TableName $Table -Filter "PartitionKey eq '$UserPartition' and SecondaryApiKey eq '$($Request['AppKey'])'"
                    if (-not $userFound) { 
                        return
                    } else {
                        $userFound.UserId
                    }
                    
                } else {
                    $env:UserName
                }
                if ($UserID) {
                    $ToInput | Add-Member NoteProperty UserID $UserID -Force 
                }
            }
        }
        
        $findChunk = if ($UniquelyNamed) {
            [ScriptBlock]::Create("
            Search-AzureTable -TableName '$Table' -Filter `"Name eq '`$(`$toInput.Name)'`"|
            Where-Object {
                `$_.PartitionKey -eq '$Partition'
            }
            "
)
        } else {
            {$false}
        }
        
        $processBlock = "
    process {
        foreach (`$v in 'ErrorAction', 'ErrorVariable', 'WarningAction', 'WarningVariable', 'OutVariable', 'OutBuffer') {
            `$null = `$psBoundParameters.Remove(`$v)
        }
        `$UserPartition = $(if ($UserPartition) { '$Userpartition' } else { '$partition'})
        `$toInput = New-Object PSObject -Property `$psBoundParameters
        $(if ($IsUserSystem) { $attachUserIdChunk })
        `$toInput.psTypenames.clear()
        `$toInput.psTypenames.add('$(if ($psboundparameters.schema) {$schema }else {$typeName })')
        `$bigItems = `$psBoundParameters.GetEnumerator() |
            Where-Object {
                `$_.Value.Length -gt 2kb
            } |
            ForEach-Object {
                `$toInput | Add-Member NoteProperty `$_.Key (Compress-Data -String `$_.Value) -Force -PassThru
            }
         
        `$found = & { $findChunk}
             
        if (-not `$found) {
            `$table = '$table'
            `$partition = '$partition'
            `$rowKey = . { $(
                if ($KeyType -eq 'GUID') {
                    {[GUID]::NewGuid()}
                } elseif ($KeyType -eq 'Hex') {
                    {"{0:x}" -f (Get-Random)}
                } elseif ($KeyType -eq 'SmallHex') {
                    {"{0:x}" -f ([int](Get-Random -Maximum 512kb))}
                } elseif ($KeyType -eq 'Sequential') {
                    {
                         
                $foundTheEnd = $false
                $toFind = 1
                $lastToFind = 0
                while (-not $FoundTheEnd) {
                    $r = Search-AzureTable -TableName $table -Filter "PartitionKey eq '$partition' and RowKey eq '$ToFind'" -Select RowKey -ErrorAction SilentlyContinue
                    if (-not $r) {
                        $foundTheEnd = $true
                    } else {
                        $lastToFind = $toFind
                        $ToFind *= 2
                    }
                }
                 
                $foundExactEnd = $false
                $start = $lastToFind
                $end = $ToFind
                $middle = $start + [int](($end - $start) / 2)
                while (-not $foundExactEnd) {
                    $r = Search-AzureTable -TableName $table -Filter "PartitionKey eq '$partition' and RowKey eq '$middle'" -Select RowKey -ErrorAction SilentlyContinue
                     
                    if ($r) {
                        # In the number space, new middle is between middle and end
                        $start = $middle
                    } else {
                        # Out of the number space, new middle is between start and middle
                        $end = $middle
                    }
                    $middle = $start + [int](($end - $start) / 2)
                    if ([Math]::Abs($start - $end) -le 1 ) {
                        $foundExactEnd = $true
                    }
                }
                $end
                         
                         
                    }
                } elseif ($KeyType -eq 'Named') {
                    {$name}
                }
            ) }
            `$toInput |
                Set-AzureTable -TableName '$table' -PartitionKey '$Partition' -RowKey `$rowKey -PassThru
        }
    }
            "
            
        $beginBlock += ('$schemaFields += "' + (($schemaFields | Where-object { $_.Name } | Select-Object -ExpandProperty Name)-join $joinWith) + '"')
        $beginBlock += '
    }
'


        if (-not $Noun) {
            $Noun = "${Table}${Partition}"
        }

        $adddescription = 
            if ($Description) {
                "Adds $description"
            } else {
                "Adds ${Noun}s"
            }



        #region Create the Commands
$out = ""


        if ($verb -contains "Add") {
$out += "function Add-$Noun {
    <#
    .Synopsis
        Adds items to $Table $Partition
    .Description
        $adddescription
    .Example
        Add-$Noun
    .Link
        Set-AzureTable
    #>
    param(
    $($fieldParameterBlock.TrimEnd(','))
    )
    $beginBlock
    $processBlock
    $endBlock
     
}
 
Set-Alias New-$Noun Add-$Noun
Set-Alias Create-$Noun Add-$Noun
 
try {
    Export-ModuleMember -Function Add-$Noun -Alias New-$Noun,Create-$Noun -ErrorAction SilentlyContinue
} catch {
    Write-Debug 'Not in a module'
}
"
         
        
        $SortString = if ($SortField) {
            if ($SortType) {
                "Sort-Object { [$SortType]`$_.$SortField } "
            } else {
                "Sort-Object {`$_.$SortField }"
            }
        } else {
            ""
        }
        
        $getdescription = 
            if ($Description) {
                "Gets $description"
            } else {
                "Gets ${Noun}s"
            }

}
        #region Read (Get-$Name, alias Read-$Name, Search-$Name, Find-$Name)

        if ($verb -contains 'Get') {
$out += @"
function Get-$Noun {
    <#
    .Synopsis
        Gets $Typename items from $table $partition
    .Description
        $getdescription
    .Example
        Get-$noun
    .Example
        Get-$noun 'search term'
    .Example
        Get-$noun -ExactName 'exact name'
    .Example
        Get-$noun -ExactName 'exact name'
    .Link
        Get-AzureTable
    .Link
        Search-AzureTable
    .Link
        Write-Crud
    #>
    $(if ($IsUserSystem) { "[CmdletBinding(DefaultParameterSetName='RowKey')]" } else { "[CmdletBinding(DefaultParameterSetName='All')]" })
    param(
    # The keyword to find within the items
    [Parameter(Mandatory=`$true,Position=0,ParameterSetName='Keyword')]
    $(if ($IsUserSystem) { "[Parameter(Position=0,ParameterSetName='MyItem')]" })
    [string]
    `$Keyword,
 
    # The properties to return back from each item
    [Parameter(ValueFromPipelineByPropertyName=`$true,Position=1)]
    [string[]]
    `$Select,
     
    # Find an item with this exact name
    [Parameter(Mandatory=`$true,ParameterSetName='ExactName')]
    $(if ($IsUserSystem) { "[Parameter(Position=1,ParameterSetName='MyItem')]" })
    [string]
    `$ExactName,
     
    # Find an item in this exact row
    [Parameter(Mandatory=`$true,ParameterSetName='RowKey',ValueFromPipelineByPropertyName=`$true)]
    [string]
    `$RowKey,
     
    [Parameter(Mandatory=`$true,ParameterSetName='All',ValueFromPipelineByPropertyName=`$true)]
    [switch]
    `$All,
     
    $(if ($IsUserSystem) {
    "
    # If set, returns only items owned by the current user id
    [Parameter(Mandatory=`$true,ParameterSetName='MyItem')]
    [Switch]`$My${Noun},
    "
    })
     
    $(if ($IsUserSystem) {
    "
    # If set, returns only items owned by a user id
    [Parameter(Mandatory=`$true,ParameterSetName='ByUserID',ValueFromPipelineByPropertyName=`$true)]
    [string]`$UserID,
    "
    })
 
 
     
    $(if ($ReadCodePartition -and $ReadCodeCrossReferenceField) {
    "
    # If set, returns only items with the matching readcode
    [Parameter(Mandatory=`$true,ParameterSetName='ByReadCode',ValueFromPipelineByPropertyName=`$true)]
    [string]`$ReadCode,
    "
    })
     
    # If set, will exclude table information
    [switch]
    `$ExcludeTableInfo
    )
     
    begin {
        `$unpackIt = {
            $unpackParameterBlock
        }
        `$storageAccount = if ((Get-WebConfigurationSetting -Setting '$StorageAccountSetting')) {
            (Get-WebConfigurationSetting -Setting '$StorageAccountSetting')
        } elseif ((Get-SecureSetting -Name '$StorageAccountSetting' -ValueOnly)) {
            (Get-SecureSetting -Name '$StorageAccountSetting' -ValueOnly)
        }
 
        `$storageKey = if ((Get-WebConfigurationSetting -Setting '$StorageKeySetting')) {
            (Get-WebConfigurationSetting -Setting '$StorageKeySetting')
        } elseif ((Get-SecureSetting -Name '$StorageKeySetting' -ValueOnly)) {
            (Get-SecureSetting -Name '$StorageKeySetting' -ValueOnly)
        }
         
    }
     
    process {
        `$selectIt = @{
            ExcludeTableInfo=`$ExcludeTableInfo
            StorageAccount = `$storageAccount
            StorageKey= `$storageKey
        }
        if (`$select) {
            `$selectIt['Select'] = `$select
        }
        if (`$psCmdlet.ParameterSetName -eq 'All') {
             
            Search-AzureTable -TableName '$table' -Where { `$_.PartitionKey -eq '$Partition' } @selectIt |
            $SortString
            ForEach-Object `$unpackIt
        } elseif (`$psCmdlet.ParameterSetName -eq 'Keyword') {
            if (`$keyword.Trim() -eq '*') {
                throw "Keyword `$keyword is too broad"
                return
            }
 
            if (-not `$select) {
                `$select = 'Name', 'Description', 'RowKey', 'PartitionKey'
            } else {
                `$select += 'RowKey', 'PartitionKey'
                `$select = `$select | Select-Object -Unique
            }
            Search-AzureTable -TableName '$table' -Where { `$_.PartitionKey -eq '$Partition' } -Select `$select|
                Where-Object {
                    `$_.Name -ilike "*`$keyword*" -or
                    `$_.Description -ilike "*`$keyword*"
                } |
                Get-AzureTable -TableName '$table' |
                $SortString
                ForEach-Object `$unpackIt
        } elseif (`$psCmdlet.ParameterSetName -eq 'ExactName') {
            Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$Partition' and Name eq '`$ExactName'" @selectIt|
            $SortString
            ForEach-Object `$unpackIt
        } elseif (`$psCmdlet.ParameterSetName -eq 'RowKey') {
            Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$Partition' and RowKey eq '`$RowKey'" @selectIt |
            $SortString
            ForEach-Object `$unpackIt
        } elseif (`$psCmdlet.ParameterSetName -eq 'ByUserID') {
            Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$Partition' and UserID eq '`$UserID'" @selectIt |
            $SortString
            ForEach-Object `$unpackIt
        } elseif (`$psCmdlet.ParameterSetName -eq 'ReadCode') {
            `$readCodeFound = Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$ReadCodePartition' and ReadCode eq '`$ReadCode'"
            `$allReadings = foreach (`$rcF in `$readCodeFound) {
                if (-not `$rcf) {continue }
                Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$Partition' and $ReadCodeCrossReferenceField eq '`$(`$rcf.RowKey)'"
            }
            `$allReadings |
                $SortString
                ForEach-Object `$unpackIt
        } elseif (`$psCmdlet.ParameterSetName -eq 'MyItem') {
            if (`$session -and `$session['User'].UserID) {
                if (-not `$keyword) { `$keyword = '*' }
                if (`$exactName) { `$keyword = `$exactName }
 
                Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$Partition' and UserID eq '`$(`$session['User'].UserID)'" @selectIt |
                $SortString
                ForEach-Object `$unpackIt |
                Where-Object {
                    `$_.Name -ilike "*`$keyword*" -or
                    `$_.Description -ilike "*`$keyword*"
                }
            } elseif (`$env:UserName) {
                Search-AzureTable -TableName '$table' -Filter "PartitionKey eq '$Partition' and UserID eq '`$(`$env:UserName)'" @selectIt |
                $SortString
                ForEach-Object `$unpackIt
            }
        }
    }
}
 
Set-Alias Read-$Noun Get-$Noun
Set-Alias Search-$Noun Get-$Noun
 
try {
    Export-ModuleMember -Function Get-$Noun -Alias Read-$Noun,Search-$Noun -ErrorAction SilentlyContinue
} catch {
    Write-Debug 'Not in a module'
}
 
"@
        
}
        #endregion Read (Get-$Name, alias Read-$Name, Search-$Name, Find-$Name)
        
        # Update (Update-$Name, alias Set-$Name)
        $processBlock = "
    process {
        `$toInput = New-Object PSObject -Property `$psBoundParameters
        $(if ($IsUserSystem) { $attachUserIdChunk })
        `$toInput.psTypenames.clear()
        `$toInput.psTypenames.add('$objTypeNAme')
        `$psBoundParameters.GetEnumerator() |
            Where-Object {
                `$_.Value.Length -gt 2kb
            } |
            ForEach-Object {
                `$toInput | Add-Member NoteProperty `$_.Key (Compress-Data -String `$_.Value) -Force
            }
                     
        `$found = Search-AzureTable -TableName '$Table' -Filter `"PartitionKey eq '$Partition' and RowKey eq '`$RowKey'`" |
            Where-Object {
                `$_.PartitionKey -eq '$Partition'
            }
             
        if (`$found) {
            if (`$found.UserId -and (`$toInput.UserId -ne `$found.UserId)) {
                Write-Error 'Item does not belong to you'
                return
            }
            `$row = `$found.rowKey
            `$null = `$toInput.psObject.Properties.Remove('Merge')
            `$null = `$toInput.psObject.Properties.Remove('RowKey')
             
            `$toInput |
                Update-AzureTable -TableName '$table' -PartitionKey '$partition' -RowKey `$rowKey -Value { `$_ } -Merge:`$merge -PassThru
        }
    }
            "
           
    $updatedescription = 
            if ($Description) {
                "Updates $description"
            } else {
                "Updates ${Noun}s"
            }

        if ($Verb -contains 'Update') {
$out+= "function Update-$Noun {
    <#
    .Synopsis
        Updates items in $Table $Partition
    .Description
        $updatedescription
    .Example
        Get-$noun -ExactName 'A Specific Item' |
            Update-$Noun -Description 'A New Description'
    .Link
        Update-AzureTable
    #>
    param(
    [Parameter(Mandatory=`$true,ParameterSetName='RowKey',ValueFromPipelineByPropertyName=`$true)]
    [string]
    `$RowKey,
     
    [switch]
    `$Merge,
     
    $($fieldParameterBlock.TrimEnd(','))
    )
    $beginBlock
    $processBlock
    $endBlock
     
}
 
Set-Alias Set-$Noun Update-$Noun
 
try {
    Export-ModuleMember -Function Update-$Noun -Alias Set-$Noun -ErrorAction SilentlyContinue
} catch {
    Write-Debug 'Not in a module'
}
"
                
}
        # Delete (Remove-$Name, alias Delete-$Name)
        $removedescription = 
            if ($Description) {
                "Updates $description"
            } else {
                "Updates ${Noun}s"
            }


        if ($verb -contains 'Remove') {
$out += @"
function Remove-$Noun
{
    <#
    .Synopsis
        Removes items from $Table $Partition
    .Description
        $removedescription
    .Example
        Get-$noun -ExactName 'A Specific Item' |
            Remove-$Noun
    .Link
        Remove-AzureTable
    #>
    [CmdletBinding(DefaultParameterSetName='RowKey',SupportsShouldProcess=`$true,ConfirmImpact='High')]
    param(
    [Parameter(Mandatory=`$true,Position=0,ParameterSetName='Keyword')]
    [string]
    `$Keyword,
     
    [Parameter(Mandatory=`$true,ParameterSetName='ExactName')]
    [string]
    `$ExactName,
     
    [Parameter(Mandatory=`$true,ParameterSetName='RowKey',ValueFromPipelineByPropertyName=`$true)]
    [string]
    `$RowKey
    )
     
    process {
        `$getParams = @{} + `$psBoundParameters
        `$getParams.Remove('WhatIf')
        `$getParams.Remove('Confirm')
        if (-not `$psBoundParameters.confirm -or (`$psBoundParameters.Confirm -eq `$false)) {
            if (-not `$psBoundParameters.whatIf) {
                `$confirmImpact = 'None'
            }
        }
        Get-$Noun @getParams |
            Where-Object {
                `$item = `$_
                `$toInput = New-Object PSObject
                $(if ($IsUserSystem) {
                    $attachUserIdChunk
                })
                 
                if (-not `$item.UserId) { return `$true }
                if (`$item.UserId -eq `$toInput.UserId) {
                    return `$true
                }
            } |
            Remove-AzureTable
    }
}
 
Set-Alias Delete-$Noun Remove-$Noun
 
try {
    Export-ModuleMember -Function Remove-$Noun -Alias Delete-$Noun -ErrorAction SilentlyContinue
} catch {
    Write-Debug 'Not in a module'
}
"@

}

$out += @"
$(if (-not $($DoNotConnect)) {
"
`$storageAccount = if ((Get-WebConfigurationSetting -Setting '$StorageAccountSetting')) {
    (Get-WebConfigurationSetting -Setting '$StorageAccountSetting')
} elseif ((Get-SecureSetting -Name '$StorageAccountSetting' -ValueOnly)) {
    (Get-SecureSetting -Name '$StorageAccountSetting' -ValueOnly)
}
 
`$storageKey = if ((Get-WebConfigurationSetting -Setting '$StorageKeySetting')) {
    (Get-WebConfigurationSetting -Setting '$StorageKeySetting')
} elseif ((Get-SecureSetting -Name '$StorageKeySetting' -ValueOnly)) {
    (Get-SecureSetting -Name '$StorageKeySetting' -ValueOnly)
}
 
 
# Connect to Azure Table Storage
`$null = Get-AzureTable -TableName '$table' -StorageAccount `$storageAccount -StorageKey `$storageKey
"
})
"@


        $out
        #endregion Create the Commands
       
        
        
    }
}