CType.ps1
# CODEWORK Need better parameter validation [Hashtable]$CType_AddedTypes = @{} Write-Verbose 'CType.ps1: adding CType base types' Add-Type -ReferencedAssemblies System.Data -TypeDefinition @' namespace CType { public class TypeElement { } public class Property : TypeElement { public string PropertyName; public string PropertyType; public string PropertyTableWidth; public string[] PropertyKeywords; } public class Parent : TypeElement { public string ParentTypeName; } public class Using : TypeElement { public string Namespace; } public class ReferencedAssembly : TypeElement { public string AssemblyName; } public class SqlWrapper : TypeElement { } public class SqlTemplateObject : TypeElement { public System.Data.DataRow DataRow; } public class NoFormatData : TypeElement { } } '@ function Add-CType { <# .SYNOPSIS Add a locally-defined type to this runspace. .DESCRIPTION Add a locally-defined type to this runspace, and optionally define table formatting for the type. These types can be used as parameter types and in OutputType([typename]) declarations. .PARAMETER TypeName Name for the new type. You can include the namespace here or else specify it separately. .PARAMETER Namespace Namespace containing the new type. If not specified, the namespace is extracted from TypeName. .PARAMETER ParentTypeName Parent class of the new class, default is System.Object .PARAMETER Using Other namespaces, typically those containing types specified in PropertyType .PARAMETER ReferencedAssemblies Other assemblies, typically those containing types specified in PropertyType .PARAMETER SqlWrapper Make the new type a SQL wrapper class for use in ConvertTo-CTypeSqlWrapper. This is set automatically if any PropertyKeyword contains SQLWrapper, or if SQLTemplateObject is set. .PARAMETER SqlTemplateObject If specified, include all properties of this SQL template object in the SQL wrapper class. Properties already specified in PropertyName are not affected. SqlWrapper is automatically set if this is specified. .PARAMETER NoFormatData Skip defining formatting metadata for this type. For example, you might want to combine all the formatting for the types in your module into a single file. .PARAMETER PropertyName Name of a property of the type being defined .PARAMETER PropertyType Type of a property of the type being defined .PARAMETER PropertyTableWidth Table width in characters of a property of the type being defined. If one or more properties specify PropertyTableWidth, a formatting directive will be generated for this type and entered into the runspace. If not specified, this property is not in the default table format. This can also be set to '*', in which case this property is displayed at full width; this should generally only be used for the last property with defined PropertyTableWidth. .PARAMETER PropertyKeywords Other keywords. The only keywords currently implemented are "Trim" which trims leading/trailing whitespace from the output string in the table formatting, and "SQLWrapper" which designates a read-only property read from the wrapped SQL object. .EXAMPLE # Define a new type PS> @" PropertyName,PropertyType,PropertyTableWidth,PropertyKeywords PrimaryTarget,string,25,Trim SecondaryTarget,string,25,Trim Weight,int,10 IsPrimary,Boolean,10 Description,String,* MainConnection,SqlConnection SecondaryConnection,SqlConnection "@ | ConvertFrom-Csv | Add-CType -TypeName MyType ` -Namespace MyCompany.Namespace ` -Using System.Data.SqlClient ` -ReferencedAssemblies System.Data # You can now use this as a full-fledged type, for example define parameters as # param( [MyCompany.Namespace.MyType]$MyParameter ) # Create an instance of the type PS> New-Object -TypeName MyCompany.Namespace.MyType -Property @{ PrimaryTarget = "String1"; SecondaryTarget = "String2"; Weight = 4; Description = "This is a very long description" } PrimaryTarget SecondaryTarget Weight IsPrimary Description ------------- --------------- ------ --------- ----------- String1 String2 4 False This is a very long description .EXAMPLE # Define a new CType Class ConvertFrom-Csv @' PropertyType,PropertyName,PropertyTableWidth,PropertyKeywords string,Name,20 string,HostName,20 DateTime,WhenCreated,25 DateTime,WhenDeleted,25 '@ | Add-CType -TypeName MyCompany.MyNamespace.MyClass # Create wrapped object New-Object -Type MyCompany.MyNamespace.MyClass -Property @{ Name = "MyName" WhenCreated = (get-date) } .EXAMPLE # Define a new SQL Wrapper Class ConvertFrom-Csv @' PropertyType,PropertyName,PropertyTableWidth,PropertyKeywords string,Name,20,SQLWrapper string,HostName,20,SQLWrapper DateTime,WhenCreated,25,SQLWrapper DateTime,WhenDeleted,25,SQLWrapper '@ | Add-CType -TypeName MyCompany.MyNamespace.MyClass -SqlWrapper # Create wrapped objects $SqlWrappedObjects = $SqlDataRows | ConvertTo-CTypeSqlWrapper -TypeName MyCompany.MyNamespace.MyClass .NOTES This method should only be called once for any class. Calling a second time is generally harmless as long as the type definition is identical, otherwise an error will be reported. Types cannot be altered once added to a process (AppDomain?). .LINK ConvertTo-CTypeSqlWrapper Add-Type Update-CTypeFormatData Update-FormatData #> [CmdletBinding()] param( [string][parameter(Mandatory=$true)]$TypeName, [string]$Namespace, [string]$ParentTypeName, [string[]]$Using, [string[]]$ReferencedAssemblies, [switch]$SqlWrapper, [System.Data.DataRow]$SqlTemplateObject, [switch]$NoFormatData, [string][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyName, [string][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyType, [string][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyTableWidth, [string[]][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyKeywords ) begin { Write-Verbose -Message @" $($MyInvocation.InvocationName): TypeName $TypeName $($MyInvocation.InvocationName): Namespace $Namespace $($MyInvocation.InvocationName): ParentTypeName $ParentTypeName $($MyInvocation.InvocationName): Using $Using $($MyInvocation.InvocationName): ReferencedAssemblies $ReferencedAssemblies $($MyInvocation.InvocationName): SqlWrapper $SqlWrapper $($MyInvocation.InvocationName): SqlTemplateObject $SqlTemplateObject "@ if (-not $Namespace) { $nameComponents = $TypeName.Split('.') if ($nameComponents.Count -lt 2) { throw 'You must specify a Namespace or else a TypeName which contains a Namespace' } $TypeName = $nameComponents[-1] $Namespace = $nameComponents[0..($nameComponents.Count - 2)] -join '.' } if ($CType_AddedTypes["$Namespace.$TypeName"]) { Write-Warning "$($MyInvocation.InvocationName): Class $Namespace.$TypeName was already added; trying anyhow" } if ($SqlTemplateObject) { $SqlWrapper = $true } $propertyList = @() } process { if ($PropertyName) { Write-Verbose -Message @" $($MyInvocation.InvocationName): PropertyName $PropertyName $($MyInvocation.InvocationName): PropertyType $PropertyType $($MyInvocation.InvocationName): PropertyTableWidth $PropertyTableWidth $($MyInvocation.InvocationName): PropertyKeywords $PropertyKeywords "@ if (-not $PropertyType) { throw 'PropertyType must be specified for all properties' } if ($PropertyTableWidth) { if ($PropertyTableWidth -ne '*') { $w = $PropertyTableWidth -as [int] if ($w -lt 1) { throw "PropertyTableWidth must be either a positive integer or '*' if it is specified" } } } $propertyList += New-Object -TypeName CType.Property -Property @{ PropertyName = $PropertyName PropertyType = $PropertyType PropertyTableWidth = $PropertyTableWidth PropertyKeywords = $PropertyKeywords } if ('SQLWrapper' -in $PropertyKeywords) { $SqlWrapper = $true } } } end { if ($SqlTemplateObject) { Write-Verbose "$($MyInvocation.InvocationName): SqlTemplateObject specified, adding additional properties" foreach ($column in $SqlTemplateObject.Table.Columns) { $name = $column.ColumnName $type = $column.DataType if ($name -in $propertyList.PropertyName) { Write-Verbose "$($MyInvocation.InvocationName): SqlTemplateObject column $type $name already in property list" } else { Write-Verbose "$($MyInvocation.InvocationName): SqlTemplateObject column $type $name must be added property list" $propertyList += New-Object -TypeName CType.Property -Property @{ PropertyName = $name; PropertyType = $type; PropertyTableWidth = 0; PropertyKeywords = @('SQLWrapper'); } } } $SqlWrapper = $true } if ($SqlWrapper) { if ($Using -notcontains 'System.Data') { $Using += 'System.Data' } if ($ReferencedAssemblies -notcontains 'System.Data') { $ReferencedAssemblies += 'System.Data' } } Write-Verbose "$($MyInvocation.InvocationName): building C# type definition" $typeDefinition = New-Object -TypeName System.Text.StringBuilder $Using = @("System") + ($Using | Where-Object {$_}) $Using | % { $null = $typeDefinition.AppendLine("using $_;") } $null = $typeDefinition.AppendLine(@" namespace $Namespace { public class $TypeName $(if ($ParentTypeName) {": $ParentTypeName"}) { "@) if ($SqlWrapper) { $null = $typeDefinition.AppendLine(@" private System.Data.DataRow WrappedSqlObject; public $TypeName(System.Data.DataRow obj) { this.WrappedSqlObject = obj; } "@) } foreach ($property in $propertyList) { $name = $property.PropertyName $type = $property.PropertyType Write-Verbose "$($MyInvocation.InvocationName) Property $type $name" # CSharp seems to be picky about spelling and capitalization of these types $typeObject = $type -as [Type] if ($typeObject.FullName) { $type = $typeObject.FullName } $propertyType = $type if ('SqlWrapper' -in $property.PropertyKeywords) { $valueExpression = "($type)val"; if ($typeObject.IsValueType) { $propertytype = "Nullable<$type>" } $null = $typeDefinition.AppendLine(@" public $propertyType $name { get { object val = this.WrappedSqlObject["$name"]; if ((val == null) || (val.GetType().FullName == "System.DBNull")) { return null; } return $valueExpression; } // get $propertyType $name } // property $propertyType $name "@) } else # -not $SqlClassWrapper { $null = $typeDefinition.AppendLine(@" public $propertyType $name { get; set; } "@) } } $null = $typeDefinition.AppendLine(@" } } "@) Write-Verbose "$($MyInvocation.InvocationName): Adding type:`n$($typeDefinition.ToString())" Add-Type -TypeDefinition $typeDefinition.ToString() -ReferencedAssemblies $ReferencedAssemblies $CType_AddedTypes["$Namespace.$classname"] = $true if (-not $NoFormatData) { $tableProperties = $propertyList | Where-Object {$_.PropertyTableWidth} if ($tableProperties) { $text = $tableProperties | Get-CTypeFormatPS1XML -TypeName "$Namespace.$TypeName" $text | Update-CTypeFormatData -TypeName "$Namespace.$TypeName" } } } } function Update-CTypeFormatData { <# .SYNOPSIS Add the specified formatting metadata to the current runspace. .PARAMETER TypeName Full typename for the type including namespace .PARAMETER FormatDataString Format data string from Get-CTypeFormatPS1XML .EXAMPLE $tableProperties = ConvertFrom-Csv @' PropertyType,PropertyName,PropertyTableWidth string,Name,20 string,HostName,20 DateTime,WhenCreated,25 DateTime,WhenDeleted,25 '@ $text = $tableProperties | Get-CTypeFormatPS1XML -TypeName "$Namespace.$TypeName" Update-CTypeFormatData -TypeName "$Namespace.$TypeName" -FormatDataString $text .LINK Get-CTypeFormatPS1XML Add-CType Update-FormatData #> [CmdletBinding()] param( [string][parameter(Mandatory=$true,Position=0)]$TypeName, [string][parameter(Mandatory=$true, ValueFromPipeline=$true)]$FormatDataString ) begin { $activity = "$($MyInvocation.InvocationName) $TypeName" $tempfile = Join-Path $env:temp "$TypeName.$(get-date -Format 'yyyyMMdd-hhmmss-fffffff').format.ps1xml" [System.Text.StringBuilder]$text = New-Object -TypeName System.Text.StringBuilder } process { $null = $text.AppendLine($FormatDataString) } end { Write-Verbose "$activity`: Adding format data to temporary file $tempfile from formatting directive:`n$FormatDataString" $FormatDataString | Set-Content -Path $tempFile try { Write-Verbose "$activity`: Updating format data" Update-FormatData -PrependPath $tempfile } catch { if (Test-Path $tempfile -ErrorAction SilentlyContinue) { Write-Verbose "$activity`: Deleting temporary file $tempfile" Remove-Item $tempfile } } } } function Get-CTypeFormatPS1XML { <# .SYNOPSIS Returns the text of a PS1XML which defines output formatting for a type. .DESCRIPTION Returns the text of a PS1XML which defines output formatting for a type. Pass this to Update-CTypeFormatData to apply this formatting to the current session. You can also save this into a PS1XML file associated with your module. .PARAMETER TypeName Name for the type .PARAMETER PropertyName Name of a property of the type being defined .PARAMETER PropertyType Type of a property of the type being defined .PARAMETER PropertyTableWidth Table width of a property of the type being defined, per Add-CType. .PARAMETER PropertyKeywords Other keywords per Add-CType. .LINK Add-CType Update-CTypeFormatData #> [OutputType([string])] [CmdletBinding()] param( [string][parameter(Mandatory=$true,Position=0)]$TypeName, [string][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyName, [string][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyType, [string][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyTableWidth, [string[]][parameter(ValueFromPipelineByPropertyName=$true)]$PropertyKeywords ) begin { $activity = "$($MyInvocation.InvocationName) $TypeName" Write-Verbose $activity # You could also build this using the [xml] class $fileHeader = @" <?xml version="1.0" encoding="utf-8" ?> <Configuration> <ViewDefinitions> "@ $templateViewHeader = @" <View> <Name>{0}</Name> <ViewSelectedBy> <TypeName>{0}</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> "@ $templateColumnHeader = @" <TableColumnHeader> <Label>{0}</Label> </TableColumnHeader> "@ $templateColumnHeaderWithWidth = @" <TableColumnHeader> <Label>{0}</Label> <Width>{1}</Width> </TableColumnHeader> "@ $headerItemSeparator = @" </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> "@ $templateColumnItem = @" <TableColumnItem> <PropertyName>{0}</PropertyName> </TableColumnItem> "@ $templateColumnItemWithTrim = @" <TableColumnItem> <ScriptBlock> (Out-String -InputObject `$_.{0}).Trim() </ScriptBlock> </TableColumnItem> "@ $viewFooter = @" </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> "@ $fileFooter = @" </ViewDefinitions> </Configuration> "@ $propertyList = New-Object System.Collections.ArrayList $fileContent = New-Object System.Text.StringBuilder -ArgumentList $fileHeader $null = $fileContent.AppendLine( ($templateViewHeader -f $TypeName) ) } process { Write-Verbose -Message @" $activity`: PropertyName $PropertyName $activity`: PropertyType $PropertyType $activity`: PropertyTableWidth $PropertyTableWidth $activity`: PropertyKeywords $PropertyKeywords "@ if ($PropertyName -and $PropertyTableWidth) { if (-not $PropertyType) { throw 'PropertyType must be specified for all properties' } if ($PropertyTableWidth) { if ($PropertyTableWidth -ne '*') { $w = $PropertyTableWidth -as [int] if ($w -lt 1) { throw "PropertyTableWidth must be either a positive integer or '*' if it is specified" } } } $obj = New-Object -TypeName CType.Property -Property @{ PropertyName = $PropertyName PropertyType = $PropertyType PropertyTableWidth = $PropertyTableWidth PropertyKeywords = $PropertyKeywords } $null = $propertyList.Add($obj) } else { Write-Verbose "$activity`: Skipping due to null name or width" } } end { foreach ($p in $propertyList) { if ($p.PropertyTableWidth) { if ($p.PropertyTableWidth -eq '*') { $null = $fileContent.AppendLine( ($templateColumnHeader -f $p.PropertyName) ) } else { $null = $fileContent.AppendLine( ($templateColumnHeaderWithWidth -f $p.PropertyName,$p.PropertyTableWidth) ) } } } $null = $fileContent.AppendLine( $headerItemSeparator ) foreach ($p in $propertyList) { if ($p.PropertyTableWidth) { if ('Trim' -in $p.PropertyKeywords) { $null = $fileContent.AppendLine( ($templateColumnItemWithTrim -f $p.PropertyName) ) } else { $null = $fileContent.AppendLine( ($templateColumnItem -f $p.PropertyName) ) } } } $null = $fileContent.AppendLine( $viewFooter ) $null = $fileContent.AppendLine( $fileFooter ) Write-Output $fileContent.ToString() } } function ConvertTo-CTypeSqlWrapper { <# .SYNOPSIS Wraps a SQL row in a wrapper object. .DESCRIPTION Wraps a SQL row in a wrapper object. The wrapper class must first be defined by Add-CType -SqlWrapper. The wrapped object has several advantages over the raw System.Data.DataRow object: (1) Property value System.DBNull is automatically converted to $null so that it is boolean-false in scripting (2) Type can be use in [OutputType([typename])] attributes (3) Type can be assigned formatting directives and other PowerShell type decorations (4) All properties are read-only (5) Hides unneeded properties of base SQL object .PARAMETER InputObject System.Data.DataRow object to be wrapped .PARAMETER TypeName Name of wrapper class, should be the same as for Add-CType .PARAMETER AddCType If specified, the type will be created automatically the first time an input object of this type is received, where the first object is a template. .PARAMETER AddCTypeScriptBlock If specified, the type will be created automatically the first time an input object of this type is received, where the first object is a template and this script block defines additional properties of the type. This implies -AddCType. Note the limitations in Notes below. .EXAMPLE $connection = New-Object System.Data.SqlClient.SqlConnection $connectionString $command = New-Object System.Data.SqlClient.SqlCommand $commandString,$connection $adapter = New-Object System.Data.SqlClient.SqlDataAdapter $command $dataset = New-Object System.Data.DataSet $null = $adapter.Fill($dataSet) $result = $tables[0].Rows $wrappedResult = $result | ConvertTo-CTypeSqlWrapper -TypeName MyCompany.MyNamespace.MyWrapperClass .NOTES Note that the type will be defined based on the first InputObject encountered. Even if subsequent objects have a different property list, the type cannot be changed. Note that you can define a type using AddCTypeScriptBlock, but because that type cannot be defined until the first template object is created, you may have difficulty using the type in an OutputType([typename]) block or a parameter definition. Consider generating the type definition with ConvertTo-CTypeDefinition and pasting the CType definition directly into your code. .LINK Add-CType CType ConvertTo-CTypeSqlDefinition #> [CmdletBinding()] param( [parameter(ValueFromPipeline=$true)][System.Data.DataRow]$InputObject, [parameter(Mandatory=$true,Position=0)][string]$TypeName, [switch]$AddCType, [ScriptBlock[]][parameter(Position=1)]$AddCTypeScriptBlock ) process { if ($AddCType -or $AddCTypeScriptBlock) { if (-not $CType_AddedTypes[$TypeName]) { CType -TypeName $TypeName -TypeElement $AddCTypeScriptBlock,(SqlTemplateObject $InputObject) } } New-Object -TypeName $TypeName -ArgumentList $InputObject } } function CType { <# .SYNOPSIS Create a new type .PARAMETER TypeName Name of the new type. Include namespace e.g. "MyCompany.MyNamespace.MyClassName" .PARAMETER TypeElement One or more script blocks or type elements which define class properties and other elements of the class. The type elements may only contain objects on the list property, sqlproperty, parent, use, ref,SqlWrapper, SqlTemplateObject, and NoFormatData, and the script blocks must evaluate to objects of those types. The script block definition must begin on the same line as the CType invocation, or must be linked with backtick. .EXAMPLE # Creates a type with 4 properties, three of which are displayed in the default formatter. CType MyCompany.MyNamespace.MyClassName { parent MyCompany.MyNamespace.MyParentClass property string PropertyName1 -PropertyTableWidth 20 -PropertyKeywords trim if ($true) { property int PropertyName2 -PropertyTableWidth 15 } property string PropertyName3 -PropertyTableWidth * property bool PropertyName4 } .EXAMPLE # Creates a type with 4 properties, three SQL wrappers and one non-wrapped property. # The non-wrapped property can be set like any other read/write property, the others # always read from the equivalent property of the wrapped DataRow object. CType MyCompany.MyNamespace.MyClassName { parent MyCompany.MyNamespace.MyParentClass sqlproperty string PropertyName1 -PropertyTableWidth 20 -PropertyKeywords trim sqlproperty int PropertyName2 -PropertyTableWidth 15 property string PropertyName3 -PropertyTableWidth * sqlproperty bool PropertyName4 } .NOTES A type with a given name can only be defined once per PowerShell runspace. If you need to change it you will need to exit the runspace where it is defined. .LINK Add-CType #> [CmdletBinding()] param( [string][parameter(Mandatory=$true,Position=0)]$TypeName, [parameter(Position=1)]$TypeElement ) $typeElements = foreach ($element in $TypeElement) { if ($element) { if ($element -is [ScriptBlock]) { Invoke-Command -ScriptBlock $element } elseif ($element -is [ScriptBlock[]]) { $element | % {Invoke-Command -ScriptBlock $_} } else { $element } } } foreach ($element in $typeElements) { if (-not ($element -is [CType.TypeElement])) { throw "$($MyInvocation.InvocationName): Invalid TypeElement $($element.GetType().FullName): TypeElement may only contain property, parent, use, ref,SqlWrapper, SqlTemplateObject, and NoFormatData, or script blocks generate them" } } [CType.Property[]]$properties = $typeElements | Where-Object {$_ -is [CType.Property]} [string[]]$parentTypes = $typeElements | Where-Object {$_ -is [CType.Parent]} | Select-Object -ExpandProperty ParentTypeName [string[]]$using = $typeElements | Where-Object {$_ -is [CType.Using]} | Select-Object -ExpandProperty Namespace [string[]]$referencedAssemblies = $typeElements | Where-Object {$_ -is [CType.ReferencedAssembly]} | Select-Object -ExpandProperty AssemblyName [System.Data.DataRow]$sqlTemplateObject = $null [System.Data.DataRow[]]$sqlTemplateObjects = @($typeElements | Where-Object {$_ -is [CType.SqlTemplateObject]} | Select-Object -ExpandProperty DataRow) if ($parentTypes) { if ($parentTypes.Count -gt 1) { throw 'You may only specify one parent type' } $parentType = $parentTypes[0] } else { $parentType = $null } if ($sqlTemplateObjects) { if ($sqlTemplateObjects.Count -gt 1) { throw 'You may only specify one SqlTemplateObject' } $sqlTemplateObject = $sqlTemplateObjects[0] } else { $sqlTemplateObject = $null } [bool]$noFormatData = [bool]($typeElements | Where-Object {$_ -is [CType.NoFormatData]} ) [bool]$sqlWrapper = [bool]($typeElements | Where-Object {$_ -is [CType.SqlWrapper]} ) $split = $TypeName.Split('.') if ($split.Count -lt 2) { throw 'You must specify full classname including namespace' } $classname = $split[-1] $namespace = $split[0..($split.Count-2)] -join '.' $properties | Add-CType -TypeName $TypeName ` -ParentTypeName $parentType ` -Using $using ` -ReferencedAssemblies $referencedAssemblies ` -SqlWrapper:$sqlWrapper ` -SqlTemplateObject $SqlTemplateObject ` -NoFormatData:$NoFormatData } function property { <# .SYNOPSIS Define a property for use in CType #> [CmdletBinding()] [OutputType([CType.Property])] param( [string][parameter(Mandatory=$true,Position=0)]$PropertyType, [string][parameter(Mandatory=$true,Position=1)]$PropertyName, [string]$width, [switch]$trim ) $propertyHash = @{ PropertyType=$PropertyType PropertyName=$PropertyName } if ($width) { $propertyHash['PropertyTableWidth'] = $width } if ($trim) { $propertyHash['PropertyKeywords'] = "Trim" } New-Object -TypeName CType.Property -Property $propertyHash } function sqlproperty { <# .SYNOPSIS Define a SQL wrapper property for use in CType #> [CmdletBinding()] [OutputType([CType.Property])] param( [string][parameter(Mandatory=$true,Position=0)]$PropertyType, [string][parameter(Mandatory=$true,Position=1)]$PropertyName, [string]$width, [switch]$trim ) $propertyHash = @{ PropertyType=$PropertyType PropertyName=$PropertyName } if ($width) { $propertyHash['PropertyTableWidth'] = $width } if ($trim) { $propertyHash['PropertyKeywords'] = ('SQLWrapper','Trim') } else { $propertyHash['PropertyKeywords'] = 'SQLWrapper' } New-Object -TypeName CType.Property -Property $propertyHash } function parent { <# .SYNOPSIS Specify a parent class for use in CType #> [CmdletBinding()] [OutputType([CType.Parent])] param( [string][parameter(Mandatory=$true,Position=0)]$ParentTypeName ) New-Object -TypeName CType.ParentType -Property @{ ParentTypeName=$ParentTypeName } } function use { <# .SYNOPSIS Specify a Using reference for use in CType #> [CmdletBinding()] [OutputType([CType.Using])] param( [string][parameter(Mandatory=$true,Position=0)]$Namespace ) New-Object -TypeName CType.Using -Property @{ Namespace=$Namespace } } function ref { <# .SYNOPSIS Specify a Referenced Assembly for use in CType #> [CmdletBinding()] [OutputType([CType.ReferencedAssembly])] param( [string][parameter(Mandatory=$true,Position=0)]$AssemblyName ) New-Object -TypeName CType.ReferencedAssembly -Property @{ AssemblyName=$AssemblyName } } function SqlWrapper { <# .SYNOPSIS Specify that a CType should be a SQL Wrapper .LINK ConvertTo-CTypeSqlWrapper #> [CmdletBinding()] [OutputType([CType.SqlWrapper])] param( ) New-Object -TypeName CType.SqlWrapper } function SqlTemplateObject { <# .SYNOPSIS Specify a SQL template object for use in CType #> [CmdletBinding()] [OutputType([CType.ReferencedAssembly])] param( [System.Data.DataRow][parameter(Mandatory=$true,Position=0)]$SqlTemplateObject ) New-Object -TypeName CType.SqlTemplateObject -Property @{ DataRow=$SqlTemplateObject } } function NoFormatData { <# .SYNOPSIS Specify that a CType should not generate formatting data .DESCRIPTION Specify that a CType should not generate formatting data. For example, you might perfer to generate the formatting data using Get-CTypeFormatPS1XML and enter this into the PS1XML for your module. #> [CmdletBinding()] [OutputType([CType.NoFormatData])] param( ) New-Object -TypeName CType.NoFormatData } function ConvertTo-CTypeDefinition { <# .SYNOPSIS Generate a string representing a CType definition for the specified SQL object .DESCRIPTION Generate a string representing a CType definition for the specified SQL object. You can paste this string into your module code to generate a CType SQL wrapper for objects of the specified class, reordering the properties and adding width and keywords to specify the formatting for the type. .LINK CType Add-CType ConvertTo-CTypeSqlWrapper #> [CmdletBinding()] [OutputType([string])] param( [System.Data.DataRow][parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]$SqlTemplateObject ) process { [System.Text.StringBuilder]$typeDefinition = New-Object -TypeName System.Text.StringBuilder -ArgumentList @" CType YourClassNameHere { SqlWrapper parent YourParentClassHere "@ foreach ($column in $SqlTemplateObject.Table.Columns) { $name = $column.ColumnName $type = $column.DataType $null = $typeDefinition.AppendLine(" sqlproperty $type $name") } $null = $typeDefinition.AppendLine('}') $typeDefinition.ToString() } } function Test-CTypeIsDefined { <# .SYNOPSIS Test whether a CType type is already defined. .DESCRIPTION Test whether a CType type is already defined. Note that types may not be removed from a PowerShell session once defined. .PARAMETER TypeName Name for the type. .EXAMPLE if (-not (Test-CTypeIsDefined MyCompany.MyNamespace.ClassName)) { CType MyCompany.MyNamespace.ClassName2 { CTypeProperty int Property1 CTypeProperty string Property2 CTypeProperty boolean Property3 } } .NOTES This method is fast, but it only detects CType types, not .NET types generally. .LINK CType Add-CType #> [CmdletBinding()] [OutputType([bool])] param( [string][parameter(Mandatory=$true,Position=0)]$TypeName ) return [bool]($CType_AddedTypes[$TypeName]) } function Test-CTypeDotNetTypeIsDefined { <# .SYNOPSIS Test whether a type is already defined. .DESCRIPTION Test whether a type is already defined. Note that types may not be removed from a PowerShell session once defined. .PARAMETER TypeName Name for the type. .EXAMPLE if (-not (Test-CTypeDotNetTypeIsDefined MyCompany.MyNamespace.ClassName)) { CType MyCompany.MyNamespace.ClassName2 { CTypeProperty int Property1 CTypeProperty string Property2 CTypeProperty boolean Property3 } } .NOTES This method uses .NET reflection to search for the type, so it can take 100-200ms to run. Use it sparingly. Consider using Test-CTypeIsDefined instead. .LINK CType Add-CType #> [CmdletBinding()] [OutputType([bool])] param( [string][parameter(Mandatory=$true,Position=0)]$TypeName ) $time = Measure-Command { $val = [bool]([appdomain]::CurrentDomain.GetAssemblies() | Where-Object DefinedTypes | Where-Object {$_.DefinedTypes.FullName -eq $TypeName}) } Write-Verbose "$($MyInvocation.InvocationName): Searching for type $TypeName took $($time.TotalMilliseconds) milliseconds" $val } |