June 15th, 2015

PowerShell Custom Type Module

Doctor Scripto
Scripter

Summary: Create custom types in Windows PowerShell with a free module by Jon Newman. Microsoft Scripting Guy, Ed Wilson, is here. Microsoft Scripting Guy, Ed Wilson, is here. Today we have a guest blogger who was with us in 2011, when he wrote Automate Facebook with a Free PowerShell Module

My name is Jon Newman, and I’m an old hand at Windows PowerShell. I was on the Windows PowerShell 1.0 team, with Bruce, Jim, and all the rest. My primary responsibility was for pipeline execution and error handling. I started at Microsoft in September 1989 (25 years ago!). I wrote management UI and infrastructure for Windows Server for 23 years, then a couple years ago, I switched to service engineering (aka operations). I created module CType for use in my operations work. I want to restrict the input to certain functions to be the custom objects that are generated by other functions (in my case, the Connection object). In addition, I have ~100 SQL queries that generate streams of System.Data.DataRow objects, and I want to manage the output objects and their formatting without having to hand code a similar number of custom C# classes. I have been using and improving CType for over six months, and it has been really useful to me. I talked about this idea with Ed at the PowerShell Summit in April 2014. Now I finally have a decent implementation and installation infrastructure. Thanks also to Jason Shirk for reviewing my work and suggesting the “CType {}” syntax.

Installing CType

If you want to cut to the chase, I have made CType available in several ways:

  • The MSI installer. This will install the CType module to your user profile.
  • The NuGet package. NuGet is good for incorporating CType into your Visual Studio project and keeping it up-to-date.
  • The Chocolatey package. Chocolatey is good for installing CType to many computers and virutal machines via automation and keeping it up-to-date.

Run Get-Help about_CType, and you are on your way!

Why create custom types in Windows PowerShell?

  1. You can specify parameters such that only an instance of your custom type can be used as input. For example, you could define:

Function New-MyConnection

{

    [OutputType([MyCompany.MyModule.Connection])]

    [CmdletBinding()]

    param(

        # parameters

        )

    # …

}

function Get-MyData

{

    [CmdletBinding()]

    param(

        [MyCompany.MyModule.Connection][parameter(Mandatory=$true)]$Connection

        # other parameters

        )

    # …

} In this example, Get-MyData will only accept an object of the type MyCompany.MyModule.Connection, which presumably was emitted by New-MyConnection. Note that only “real” .NET types will work for this purpose—it isn’t enough to simply add TypeName as a decoration with $connection.PSObject.TypeNames.Insert(0,$typename).

  1. You can decorate the type with formatting metadata and other Windows PowerShell type decorations. In this case, it actually would be enough to call $obj.PSObject.TypeNames.Insert(0,$typename).
  2. Windows PowerShell has other issues with the raw System.Data.DataRow class, which I will discuss later.

Why use CType to create custom types?

Add-Type gives you complete flexibility to define your custom types. You could use Add-Type plus Update-FormatData directly and not bother with CType. However, there are a number of reasons why you might want to use CType:

  1. Add-Type requires you to define your class in CSharp, JScript, or VisualBasic. Scripters may not be comfortable in these programmer languages.
  2. These programming languages make it difficult to create type definitions “downstream in the pipeline,” which well-constructed Windows PowerShell commands can do. For example, the Add-CType parameters include:

[string][parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]$PropertyName,

        [string][parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]$PropertyType, CType takes care of translating this into C#. This allows you to generate types dynamically and in a concise manner. I will demonstrate this later.

  1. Update-FormatData requires input in a very cumbersome XML schema. CType also takes care of generating this. The current implementation of CType only supports simple table formats.

What about PowerShell 5.0 class declarations?

Windows PowerShell 5.0 introduces syntax for class declarations, where Desired State Configuration (DSC) is the first target scenario. These are “real” .NET classes, and they can be used as function parameter types. (At this writing, I can only comment on the implementation of Windows PowerShell class declarations in the WMF 5.0 November 2014 Tech Preview.) You can define your classes by using Windows PowerShell 5.0 class declarations rather than CType if you like; however, there are several advantages to using the CType module:

  1. CType does not require WMF 5.0. Many customers will not install the still-to-be-released (at this writing) official Windows PowerShell 5.0 across all their servers for years, especially customers with dependencies on previous versions of Windows PowerShell (like old versions of System Center).
  2. The class declaration syntax in CType isn’t really all that different from CSharp. As with Add-Type, it would be difficult to create Windows PowerShell class declarations “downstream in the pipeline.”
  3. At this writing, Windows PowerShell class declarations only support short class names. They do not currently support namespaces or class inheritance.

   Note It would be possible to implement a variant of CType layered over Windows PowerShell classes rather than over
   Add-Type. I can’t identify any compelling advantages in making that change. There is nothing wrong with Windows
   PowerShell classes—they just aren’t targeted at this scenario.

Creating a type with function CType

CType is very easy to use. Simply call CType to add each of your custom types. If you are writing a module, add CType to RequiredModules in your .psd1 file, and create the types in the module initialization for your module. Note that a .NET type can only be defined once, so you do not want to call CType for any class more than once. Here is an example:

CType MyCompany.MyNamespace.MyClass {

    property string Name -Width 20

    property string HostName -Width 20

    property DateTime WhenCreated -Width 25

    property DateTime WhenDeleted -Width 25

} When the type is defined, you can create an instance by using New-Object. You don’t have to specify all the properties, and you can change their values at any time.

New-Object –TypeName MyCompany.MyNamespace.MyClass –Property @{

       Name = “StringValue”

       WhenCreated = [DateTime]’2015.01.01′

       WhenDeleted = [DateTime]::Now

       } One cool thing about this syntax is that you can use Windows PowerShell structures such as loops, and conditionals, inside the CType declaration. Here is a very simple example:

CType MyCompany.MyNamespace.MyClass {

    property string Name -Width 20

    property string HostName -Width 20

    if ($IncludeTimePropertiesWithThisType)

    {

        property DateTime WhenCreated -Width $DateTimeWidth

        property DateTime WhenDeleted -Width $DateTimeWidth

    }

}

Creating a type with Add-CType

If you want to create types by using classic Windows PowerShell pipelines rather than the “little language” for function CType, this is an alternate syntax that does exactly the same thing.    Note  New-Object is only one way to come up with these objects. Select-Object, ConvertFrom-CSV, or any other way to
   generate objects with properties PropertyType and PropertyName (and optionally PropertyTableWidth and/or
   PropertyKeywords) will work.

@(

(New-Object PSObject -Property @{PropertyType=’string’;   PropertyName=’Name’;        PropertyTableWidth=20}),

(New-Object PSObject -Property @{PropertyType=’string’;   PropertyName=’HostName’;    PropertyTableWidth=20}),

(New-Object PSObject -Property @{PropertyType=’DateTime’; PropertyName=’WhenCreated’; PropertyTableWidth=25}),

(New-Object PSObject -Property @{PropertyType=’DateTime’; PropertyName=’WhenDeleted’;

PropertyTableWidth=25}) `

) | Add-CType -TypeName MyCompany.MyNamespace.MyClass I actually wrote Add-CType first, then Jason Shirk suggested the “function CType” syntax.

SQL wrapper classes

If you have created wrappers for SQL queries by using Windows PowerShell, you may have noticed some idiosyncrasies using the System.Data.DataRow class in Windows PowerShell, for example: System.DBNull: If a particular result row does not have a defined value for a particular column, you may see some property values come back as a reference to the singleton instance of type System.DBNull. This has the advantage of distinguishing between an empty cell and an actual zero or empty string result value. Unfortunately, when Windows PowerShell converts System.DBNull to Boolean, it comes out as $true, so statements like…

if ($row.Property) {DoThis()} …will actually execute DoThis() when the value is System.DBNull. This is pretty confusing, but it probably can no longer be fixed in Windows PowerShell without breaking backward compatibility. Without the wrapper class, you would have to use a workaround like this:

if (($row.JoinedProperty –notis [System.DBNull]) –and $row.JoinedProperty) {DoThis()} The wrapper class takes care of this problem, by turning the System.DBNull value back to null.

Extra methods

System.Data.DataRow objects contain SQL-specific methods, like BeginEdit(), which are probably not relevant to users of your script. The wrapper hides these methods. You create SQL wrapper classes like this:

CType MyCompany.MyNamespace.MyWrapperClass {

    sqlproperty string Name -Width 20

    sqlproperty string HostName -Width 20

    sqlproperty DateTime WhenCreated -Width 25

    sqlproperty DateTime WhenDeleted -Width 25

} ~or~

@(

(New-Object PSObject -Property @{PropertyType=’string’;   PropertyName=’Name’;        PropertyTableWidth=20}),

(New-Object PSObject -Property @{PropertyType=’string’;   PropertyName=’HostName’;    PropertyTableWidth=20}),

(New-Object PSObject -Property @{PropertyType=’DateTime’; PropertyName=’WhenCreated’; PropertyTableWidth=25}),

(New-Object PSObject -Property @{PropertyType=’DateTime’; PropertyName=’WhenDeleted’;

PropertyTableWidth=25}) `

) | Add-CType -TypeName MyCompany.MyNamespace.MyWrapperClass -SqlWrapper You create instances like this:

$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 | ConvertTo-CTypeSqlWrapper -ClassName MyCompany.MyNamespace.MyWrapperClass If you already have functions that generate DataRow objects, use ConvertTo-CTypeDeclaration to create an initial CType wrapper for them. Simply add your type name, add a parent type name (or remove that clause), add widths, and change the property order as desired for formatting, and your declaration is ready! That’s it! You can contact me through the following Comment section. I would love to see bugs, suggestions, questions, and new scenarios. Feedback is really important to me—it’s how I decide whether to invest more time in a project like this one. I also have a site on GitHub where you can report issues: Welcome to Issues! Also, please contact me if you have any interest in assisting with TeslaFacebookModule. (“Start-Car” anyone?) And tell Elon Musk to hurry up… ~Jon Thank you, Jon. I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace. Ed Wilson, Microsoft Scripting Guy

Author

The "Scripting Guys" is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.

0 comments

Discussion are closed.