diff --git a/CHANGELOG.md b/CHANGELOG.md index 9166c3a..04672e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ +# 1.1.0 + +Added `Test-CTypeDataMember` and `Add-CTypeData` functions for testing if a type has any defined custom type data and +adding custom type data, respectively. Defining type data with .ps1xml files can result in errors importing the same +module multiple times: PowerShell complains that the type data is already defined. Using `Add-CTypeData` prevents these +this error as it only adds members that don't already exist. + + # 1.0.0 ## Upgrade Instructions @@ -23,7 +31,7 @@ a string, and pass that string to the `Command` parameter. This will base64 enco PowerShell's -EncodedCommand property. * The `Invoke-CPowerShell` function no longer has `FilePath`, `OutputFormat`, `ExecutionPolicy`, `NonInteractive`, or `Runtime` parameters. Instead, pass these as arguments to the `ArgumentList` parameter, e.g. -`-ArgumentList @('-NonInteractive', '-ExecutionPolicy', 'Bypasss'). You are now responsible for passing all PowerShell +`-ArgumentList @('-NonInteractive', '-ExecutionPolicy', 'Bypasss')`. You are now responsible for passing all PowerShell arguments via the `ArgumentList` parameter. * The `Invoke-CPowerShell` function no longer supports running PowerShell 2 under .NET 4. * Remove the `-Encode` switch. `Invoke-CPowerShell` now always base64 encodes the value of the `Command` parameter. diff --git a/Carbon.Core/Carbon.Core.psd1 b/Carbon.Core/Carbon.Core.psd1 index ae907e4..40bc607 100644 --- a/Carbon.Core/Carbon.Core.psd1 +++ b/Carbon.Core/Carbon.Core.psd1 @@ -18,7 +18,7 @@ RootModule = 'Carbon.Core.psm1' # Version number of this module. - ModuleVersion = '1.0.0' + ModuleVersion = '1.1.0' # ID used to uniquely identify this module GUID = '20DA9F42-23C4-4917-8597-DCFD7EE4AD00' @@ -76,12 +76,14 @@ # Functions to export from this module. Only list public function here. FunctionsToExport = @( + 'Add-CTypeData', 'ConvertTo-CBase64', 'Get-CPowerShellPath', 'Invoke-CPowerShell', 'Start-CPowerShellProcess', 'Test-COperatingSystem', - 'Test-CPowerShell' + 'Test-CPowerShell', + 'Test-CTypeDataMember' ) # Cmdlets to export from this module. By default, you get a script module, so there are no cmdlets. diff --git a/Carbon.Core/Functions/Add-CTypeData.ps1 b/Carbon.Core/Functions/Add-CTypeData.ps1 new file mode 100644 index 0000000..55ffdbe --- /dev/null +++ b/Carbon.Core/Functions/Add-CTypeData.ps1 @@ -0,0 +1,115 @@ +function Add-CTypeData +{ + <# + .SYNOPSIS + Adds type data to a type only if the type data doesn't already exist. + + .DESCRIPTION + The `Add-CTypeData` function uses PowerShell's `Update-TypeData` cmdlet to add type data to a type, but only if the + given type data doesn't already exist. Pass the type to the `Type` parameter or the type name to the `TypeName` + parameter, the new type data member type to the `MemberType` parameter (e.g. `AliasProperty`, `NoteProperty`, + `ScriptProperty`, or `ScriptMethod`), the member name to the `MemberName` parameter, and the member's + value/implementation to the `Value` parameter. + + Note that the `Type` parameter should be the bare name of the type, e.g. `Diagnostics.Process`, *without* square + brackets. + + If the type already has an equivalent member with the name given by the `MemberName` parameter, nothing happens, and + the function returns. + + .EXAMPLE + Add-CTypeData -Type Diagnostics.Process -MemberType ScriptProperty -MemberName 'ParentID' -Value $scriptBlock + + Demonstrates how to create a script property on a type. In this example, the `System.Diagnostics.Process` type will + be given a `ParentID` property that runs the code in the script block in the `$scriptBlock` variable. + + .EXAMPLE + Add-CTypeData -Type Diagnostics.Process -MemberType ScriptMethod -MemberName 'GetParentID()' -Value $scriptBlock + + Demonstrates how to create a script method on a type. In this example, the `System.Diagnostics.Process` type will + be given a `GetParentID()` method that runs the code in the script block in the `$scriptBlock` variable. + + .EXAMPLE + Add-CTypeData -Type Diagnostics.Process -MemberType AliasProperty -MemberName 'ProcessId' -Value 'Id' + + Demonstrates how to create an alias script property on a type. In this example, the `System.Diagnostics.Process` + type will be given a `ProcessId` property that is an alias to the 'Id' property. + + .EXAMPLE + Add-CTypeData -Type Diagnostics.Process -MemberType NoteProperty -MemberName 'ParentID' -Value $parentPid + + Demonstrates how to create a ntoe property on a type. In this example, the `System.Diagnostics.Process` type will + be given a `ParentID` property that returns the value in the `$parentPid` variable. + #> + [CmdletBinding()] + param( + # The type on which to add the type data. This should be the bare type name, e.g. Diagnostics.Process, *not* + # the type surrounded by square brackets, e.g. `[Diagnostics.Process]`. + [Parameter(Mandatory, ParameterSetName='ByType')] + [Type] $Type, + + # The name of the type on which to add the type data. + [Parameter(Mandatory, ParameterSetName='ByTypeName')] + [String] $TypeName, + + # The member type of the new type data. Only `AliasProperty`, `NoteProperty`, `ScriptProperty`, `ScriptMethod` + # are supported. + [Parameter(Mandatory)] + [ValidateSet('AliasProperty', 'NoteProperty', 'ScriptProperty', 'ScriptMethod')] + [Management.Automation.PSMemberTypes] $MemberType, + + # The type data's member name. + [Parameter(Mandatory)] + [String] $MemberName, + + # The value for the member. If `MemberName` is: + # + # * `AliasProperty`, this should be the name of the target property. + # * `NoteProperty`, the literal value of the property. + # * `ScriptProperty`, a script block that return's the property value. + # * `ScriptMethod`, a script block that implements the method logic. + [Parameter(Mandatory)] + [Object] $Value + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + $memberTypeMsg = '{0,-14}' -f $MemberType + + if( -not $TypeName ) + { + $TypeName = $Type.FullName + } + + if( $Type ) + { + if( $MemberType -like '*Property' ) + { + if( ($Type.GetProperties() | Where-Object Name -EQ $MemberName) ) + { + Write-Debug ("Type $($memberTypeMsg) [$($TypeName)] $($MemberName)") + return + } + } + elseif( $MemberType -like '*Method') + { + if( ($Type.GetMethods() | Where-Object Name -EQ $MemberName) ) + { + Write-Debug ("Type $($memberTypeMsg) [$($TypeName)] $($MemberName)") + return + } + } + } + + $typeData = Get-TypeData -TypeName $TypeName + if( $typeData -and $typeData.Members.ContainsKey($MemberName) ) + { + Write-Debug ("TypeData $($memberTypeMsg) [$($TypeName)] $($MemberName)") + return + } + + Write-Debug ("TypeData + $($memberTypeMsg) [$($TypeName)] $($MemberName)") + Update-TypeData -TypeName $TypeName -MemberType $MemberType -MemberName $MemberName -Value $Value +} + diff --git a/Carbon.Core/Functions/Test-CTypeDataMember.ps1 b/Carbon.Core/Functions/Test-CTypeDataMember.ps1 new file mode 100644 index 0000000..0bf6332 --- /dev/null +++ b/Carbon.Core/Functions/Test-CTypeDataMember.ps1 @@ -0,0 +1,44 @@ + +function Test-CTypeDataMember +{ + <# + .SYNOPSIS + Tests if a type has an extended type member defined. + + .DESCRIPTION + `Test-CTypeDataMember` tests if a type has an extended type member defined. If the type isn't found, you'll get an + error. + + Returns `$true` if the type is found and the member is defined. Otherwise, returns `$false`. + + .EXAMPLE + Test-CTypeDataMember -TypeName 'Microsoft.Web.Administration.Site' -MemberName 'PhysicalPath' + + Tests if the `Microsoft.Web.Administration.Site` type has a `PhysicalPath` extended type member defined. + #> + [CmdletBinding()] + [OutputType([bool])] + param( + # The type name to check. + [Parameter(Mandatory)] + [String] $TypeName, + + # The name of the member to check. + [Parameter(Mandatory)] + [String] $MemberName + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + $typeData = Get-TypeData -TypeName $TypeName + if( -not $typeData ) + { + # The type isn't defined or there is no extended type data on it. + return $false + } + + return $typeData.Members.ContainsKey( $MemberName ) +} + +