Managing Linux via OMI: Packaging

PowerShell-Control-Linux-Packaging

This is sixth and final part of the series. You can find outline of the whole series here. We already have OMI server up, running and accessible from outside and we have test Lin_Process implemented on it, with several properties and single method: Kill. Today we will build our own CDXML-based module that will surround remote CIM calls in cmdlet-like functions. This package won’t be auto-generated, I had no luck finding procedure to do that (truth be told: I haven’t tried hard and wanted to learn CDXML concepts anyway). We move back to PowerShell than…

Learn by example or Read The Friendly Manual

Writing CDXML (stands for: Cmdlet Definition XML) may be difficult if you don’t know schema and don’t have any reference implementation that you could use as a template. Luckily, many of new modules added in Windows 8/ Windows Server 2012 are based on CDXML. You can find all CDXML files that are part of system modules very quickly:

Get-Module -ListAvailable |             
    where ModuleBase -Like $PSHOME* |             
    Get-ChildItem -Filter *.cdxml -Path { $_.ModuleBase } |             
    foreach FullName

On my box I’ve found almost 300 of files that I could use as a starting point or guidance on how that can be done. Another option is using documentation, Otto Helweg pointed me in the right direction suggesting this page.

Let’s walk very quickly thru basic structure of such files, and than we will look at implementation of two commands: Get-LinProcess And Stop-LinProcess.

CDXML structure

According to documentation (and as you can probably see in CDXML files present on your system) these files are usually defined around classes in CIM. You start of with stating what class do you plan to implement, together with noun that will be used for cmdlets working on this class, example from above mentioned documentation:

<PowerShellMetadata xmlns=http://schemas.microsoft.com/cmdlets-over-objects/2009/11>

  <Class ClassName=root\cimv2\Win32_Process>

    <Version>2.0.0.0</Version>

    <DefaultNoun>Win32Process</DefaultNoun>

  </Class>

</PowerShellMetadata>

Inside we can define few groups:

  • InstanceCmdlets (Get, Set usually fall into that category)
  • StaticCmdlets (New)
  • Enums (that can be used to translate CIM numbers into meaningful names)

In our scenario we need only first group. Inside this group you can define three types of elements:

  • GetCmdletParameters – global (shared between cmdlets) set of parameters that can be used to retrieve instances of given class (same node can be used inside cmdlet definition)
  • GetCmdlet – special class used for Get-DefaultNoun cmdlet
  • Cmdlet – any other instance related cmdlets, in our case – Stop.

Inside each cmdlet definition we need to specify metadata of cmdlet (verb, help uri, confirm impact), it’s parameters (including pipeline binding flags), we can even perform validation, e.g. I defined parameter –State that has closed list of possible values:

<Property PropertyName=State>

    <Type PSType=string />

    <RegularQuery>

        <CmdletParameterMetadata IsMandatory=false

            CmdletParameterSets=ById ByName>

            <ValidateSet>

                <AllowedValue>D</AllowedValue>

                <AllowedValue>R</AllowedValue>

                <AllowedValue>S</AllowedValue>

                <AllowedValue>T</AllowedValue>

                <AllowedValue>W</AllowedValue>

                <AllowedValue>X</AllowedValue>

                <AllowedValue>Z</AllowedValue>

            </ValidateSet>

        </CmdletParameterMetadata>

    </RegularQuery>

</Property>

 

You can see that this is really pretty easy to follow. Another thing that we need to do is select method that we will implement: for GetCmdlet this is always cim:GetInstance, so we do not need to specify it. For others it’s either intrinsic (cim:Operation) or extrinsic (instance method name). If method uses parameters, we can define them too (and let user pick value).

What’s his name, what’s his number

We will first take a look at implementation of Get-LinProcess. We will start with parameters. There are three defined: Name, PID and State. We already have seen definition of state. Looking at Name we can see how can we implement support for filtering at this point:

<Property PropertyName=Name>

    <Type PSType=string />

    <RegularQuery AllowGlobbing=true>

        <CmdletParameterMetadata

            IsMandatory=false

            Position=0

            ValueFromPipelineByPropertyName=true

            CmdletParameterSets=ByName

        />

    </RegularQuery>

</Property>

 

Definition is similar to [Parameter()], the only difference is that here we use XML, and that we can specify if command should support globbing. We define this parameter as optional to support running command without parameters to list all processes. Last parameter belongs to separate ParameterSet, and is therefore mandatory (to make selection of set for binder easy). Defining parameters is all we really need to do in this type of commands.

What If I Stop…

Stop-LinProcess is different story: first of all we have a method to map with this cmdlet. We will “hide” kill method inside our cmdlet. As you probably remember our method takes only one parameter: Signal:

<Method MethodName=Kill>

<Parameters>

    <Parameter ParameterName=Signal>

        <Type PSType=Uint32/>

        <CmdletParameterMetadata

            IsMandatory=false

            Position=1

        >

            <ValidateNotNull />

            <ValidateNotNullOrEmpty />

        </CmdletParameterMetadata>

    </Parameter>

</Parameters>

</Method>

As you can see, this kind of parameter is defined inside “Method” node that is child of “Cmdlet” node. But that is not enough: we need to tell our command what processes do we want to stop. We define two, both mandatory (otherwise running Stop-LinProcess without any parameters would kill our server). Both parameters will also support piping value to them by property name, as an example PID parameter:

<Property PropertyName=PID>

    <Type PSType=uint32 />

    <RegularQuery>

        <CmdletParameterMetadata

            IsMandatory=true

            Aliases=ID ProcessID

            ValueFromPipelineByPropertyName=true

            CmdletParameterSets=ById

        />

    </RegularQuery>

</Property>

Finally, because we would like to support ShouldProcess, and our provider is not designed in a way that would allow it on remote side, we need to make sure that PowerShell will honour –WhatIf ad –Confirm if user decides to use them. To move responsibility on client side, we need one extra information (this should be just below “Class” level):

<CmdletAdapterPrivateData>

    <Data Name=ClientSideShouldProcess />

</CmdletAdapterPrivateData>

This change is key to make our command really similar in a way it behaves to regular cmdlets, unless we already implemented this kind of support on remote side. Something I would love to do next. List is longer, I would love to:

  1. Implement Lin_Process class on top of CIM_UnixProcess class
  2. Implement New-LinProcess
  3. Add ShouldProcess support to Stop-LinProcess on provider side.

For know – I’m pretty happy with what I got. Take a look:

PowerShell-OMI-Stop-LinProcess-WhatIf

And that’s it (for now) – I hope this series encouraged some of you to give it a try with OMI and/ or CDXML modules.

Advertisement

1 thought on “Managing Linux via OMI: Packaging

  1. Pingback: Managing Linux via OMI: Roadmap | IT Pro PowerShell experience

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s