Binary (C#) Implementation of Resource failing on Property Definitions

This topic contains 11 replies, has 3 voices, and was last updated by Profile photo of Texylvanian Texylvanian 4 months, 2 weeks ago.

  • Author
    Posts
  • #47545
    Profile photo of Texylvanian
    Texylvanian
    Participant

    I'm am getting so close to enjoying the ultimate success on my first Custom DSC configuration. So you know that's when I'm going to get this error.

    I'm creating a custom resource using C#. I used xDscResource to generate the mof and file structure, and then plugged my dll into it. When I try to run the configuration using Start-DscConfiguration, I get the following message:

    The command Test-TargetResource of the PS module MongoDbServiceConfiguration does not implement the write property
    Ensure mentioned in the corresponding MOF schema file C:\Program Files\WindowsPowerShell\Modules\cTOPAZConfig\DscResour
    ces\MongoDbServiceConfiguration\MongoDbServiceConfiguration.schema.mof. All write paramenters mentioned in the schema
    file must be implemented by the command Test-TargetResource.
    + CategoryInfo : InvalidOperation: (root/Microsoft/...gurationManager:String) [], CimException
    + FullyQualifiedErrorId : WriteParameterNotImplemented
    + PSComputerName : localhost

    I used the following to create the xDscResourceProperty:

    $Ensure = New-xDscResourceProperty -Name Ensure -Type String -Attribute Write -ValidateSet "Present","Absent"
    

    and then this to create the resource (I defined all the properties using New-xDscResourceProperty, but am trying to focus on the problem-child):

    New-xDscResource -Name "MongoDbServiceConfiguration" -Property $ConfigFilePath,$MongoDbExePath,$MongoDbDatabasePath,$Ensure,$HasSecureMongoDb,$Username,$Password,$IsEncrypted,$MongoDbExecutableName,$MongoDbShellName -FriendlyName "XXXXXXXXMongoDbConfig" -ClassVersion 1.0 -Path "C:\Temp\Modules\cXXXXXConfig"
    

    I've gotten similar errors after making it required, and I even tried to remove the Value map to see if it would line up, as I suspect that's where the issue is. However I continue to get the error, so I'm pretty sure I need way to declare "ValueMap" in the schema.mof and a way to declare the exact same thing in the code.

    Schema.mof:

    [ClassVersion("1.0"), FriendlyName("XXXXXMongoDbConfig")]
    class MongoDbServiceConfiguration : OMI_BaseResource
    {
        [Key] String ConfigFilePath;
        [Required] String MongoDbExePath;
        [Required] String MongoDbDatabasePath;
        [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
        [Write] Boolean HasSecureMongoDb;
        [Write] String Username;
        [Write] String Password;
        [Write] Boolean IsEncrypted;
        [Write] String MongoDbExecutableName;
        [Write] String MongoDbShellName;
    };
    

    Property implementation in C#:

        [OutputType(typeof(System.Collections.Hashtable))]
        [Cmdlet(VerbsCommon.Get, "TargetResource")]
        public class GetTargetResource : PSCmdlet
        {
            [Parameter(
                Mandatory = false
                )]
            [ValidateSet("Present", "Absent", IgnoreCase = true)]
            public string Ensure { get; set; } = "Present";
    

    Any ideas? The code is working as a standalone cmdlet (I've got another question about that in a separate thread) as well as running nicely in nUnit tests in C#.

  • #47557
    Profile photo of Don Jones
    Don Jones
    Keymaster

    So....... my C# foo is not as strong as when I was younger, so we'll see if Steve or Dave jump in. And I'll ping them.

    But a cmdlet can't directly be a resource, I don't think. You're meant to declare a class that is a resource, and the class has Get/Set/Test methods. That isn't what you're doing.

    I think I'll go back to the comment I made on the other thread you posted. You should do a plain binary module with your cmdlets. Then write a function- or class-based script resource that implement the DSC-needed Get/Set/Test pattern. Your cmdlets would then be usable via two different interfaces – directly from the shell by just running commands, and from the "interface" of a DSC resource calling those cmdlets. Your Resource might not have much code in it, and that's all the better.

    Take xADUser as an example. Microsoft just calls their existing -ADUser commands – they didn't write a dedicated binary module just to be a resource. The Resource is meant to be a wrapper around existing commands, not define new functionality in and of itself.

    • This reply was modified 4 months, 3 weeks ago by Profile photo of Don Jones Don Jones.
  • #47560
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Well, https://blogs.msdn.microsoft.com/powershell/2014/05/29/wish-i-can-author-dsc-resource-in-c/ proves I'm wrong about the code (shock), but I stand by my architectural approach. Don't write a binary resource; write a binary module, and wrap a script resource around it.

  • #47562
    Profile photo of Don Jones
    Don Jones
    Keymaster

    And to be fair, the error is complaining about Test-TargetResource, but you shared the code for your Get.

    But I still stand by my architecture comment ;).

  • #47580
    Profile photo of Texylvanian
    Texylvanian
    Participant

    Before I get too deep into this, the community's and your help and responsiveness is greatly appreciated. In my case, I am a career developer trying to make DevOps work in a very small company without a lot of resources. So without Community Support, we may not have been otherwise able to make this transition. So Thank You Thank You Thank You! (sorry if too effusive, but whenever I take a chunk of someone else's time, I want to show thanks).

    On to the meat. You said, "…a cmdlet can't directly be a resource, I don't think. You're meant to declare a class that is a resource, and the class has Get/Set/Test methods. That isn't what you're doing."

    I'm creating individual classes for the Get/Set/Test methods, per the MSDN article, Authoring a DSC resource in C#, which I presume to be Microsoft's official guidance. And "Wish I can author…" (which I hadn't seen) provides further support. There does seem to be a bias in the community towards the Ops approach, which is understandable. But tend to research and be biased towards my strengths, and the solutions I choose will trend that way.

    I need to learn a more about class-based resources and see about using them to wrap binaries before I fully understand your suggested architecture. Initially, it appears a to be counter-intuitive on two fronts. The first, as I mentioned earlier, is that there are cmdlet functions available in C#, such as WriteVerbose, that are unavailable unless inheriting from PSCmdlet. So my binaries would be limited when trying to write output, & etc. The second concern is maintainability. We are primarily a developer shop, and our existing Ops folks are pretty far away from maintaining PowerShell scripts, much less C#. On the flip side, our Dev folks are really strong in C# and know a bit of PowerShell. Having a solution that depends on mixing technologies doesn't seem to lend itself to maintainability on the surface. So I chose the C# solution to target the folks most likely to maintain it, and although there is still a PS learning curve it is much easier for developers to pick up. That being said, I am taking pains to follow guidance and convention so that when Community support is needed, it won't be incomprehensible.

    And yeah, oops on the pasting the Get code. Virtually identical though (and am editing the original post), as the original error message clearly states that properties need to be the same across the Get/Set/Test.

    All that being said, my original issue (not getting the property declarations to match between the MOF and C#) still stands. "Wish I can author…" suggests roughly the same implementation that I've got and it seems like I'm faced with a choice of continuing to push for a possibly insoluble answer or yet another re-write.

    I'll post back once I've got the solution (then see about refactoring the code to support both Resource and Module implementations).

  • #47582
    Profile photo of Texylvanian
    Texylvanian
    Participant

    Can't seem to edit the original post, but here's the code from the Test method:

        [Cmdlet("Test", "TargetResource")]
        [OutputType(typeof(bool))]
        public class TestTargetResource : PSCmdlet
        {
            #region Properties
    
            [ValidateSet("Present", "Absent", IgnoreCase = true)]
            public string Ensure { get; set; } = "Present";
    
        
        }
    
    

    And I just realized I'm using C# 4.0 initialization on the property. I'll roll that back to a more traditional property with a backing field and see if that solves the issue (not sure how the new property is implemented under the hood, but my bet is that the under-the-hood is how PowerShell sees it).

  • #47584
    Profile photo of Don Jones
    Don Jones
    Keymaster

    So a couple of things.

    There's actually almost nothing available in C# that isn't also available in script; any function using [CmdletBinding()] can use Write-Verbose in exactly the same way. And Write- everything else, for that matter, including Write-Information, which in v5 is definitely a good thing since it can more easily write to persistent system logs. Advanced Functions (those using [CmdletBinding()] were originally called "script cmdlets" for good reason.

    But the overall idea here is to separate functionality from presentation. Just as you'd pack most of your actual code into a DLL, and then call that DLL from a GUI front-end, so it should be in PowerShell. Write your code as cmdlets, and then simply call those cmdlets, in as little code as possible, from a resource. That way your code isn't tied to the resource model/format. This is exactly how Microsoft proceeded: they invested in cmdlets for 10 years, and then built resources that simply call those cmdlets. Just write cmdlets that work really well, and then call those from a resource.

    Even when writing script resources, that's the model most of us are advocating. Write a normal module – a set of functions – that "do" everything. Test those commands (functions) as a normal module. Then, write a function- or class-based resource to just call those commands in whatever sequence is needed. The resource should be a thin layer that "adapts" your cmdlets to the model required by the LCM. Your C# code only needs to be a "normal" module; the resource "adapts" that to DSC. In the same vein, the Exchange module is a "normal" module; they have a GUI which "adapts" those commands into people's eyeballs.

  • #47586
    Profile photo of Texylvanian
    Texylvanian
    Participant

    I think I begin to see. The issue isn't with writing the module in C#, it's writing the resource as C#. Instead, I should go ahead and leave the module as a C# binary, but implement the DSC as a wrapper around it. Which is exactly the path I initially took until I saw the article I referenced (i.e. the Get/Set/Test classes) looking for how to wrap.

    I have a couple more hours for this today. I can roll-back the Get/Set/Test pretty handily to a module, then play with wrapping that. That's a fair (and already soundly tested) approach.

  • #48243
    Profile photo of Texylvanian
    Texylvanian
    Participant

    This is to wrap things up (had a long weekend and much busy-ness, so haven't got to this until now). Don's recommendation was spot on, although my implementation may be more developer-centric. Ultimately, I created a DSC Resource that just wraps the C# module. There is absolutely NO logic in it at all – just parameters and the call. (This is also how many front-ends are built in some design patterns). Treating the PowerShell as a "front-end" was a very apt analogy and it was a real joy to see it work, not only as a standalone module, but also as a DSC Resource.

    Definitely learned a lot, and I think my meanderings helped me to learn it more and deeper. I now have the pattern to use for the next 5 configs (and the default code base), so they should go a LOT faster.

    • This reply was modified 4 months, 2 weeks ago by Profile photo of Texylvanian Texylvanian. Reason: clarification. next time I'll proof it more
  • #48329
    Profile photo of Arie H
    Arie H
    Participant

    Now you just need to publish it on GitHub as a template for others to see and add an article on Powershell.org to describe the process and the outcome 😉

  • #48331
    Profile photo of Don Jones
    Don Jones
    Keymaster

    I'll second that. Email webmaster@ if you'd like blogging permissions!

  • #48333
    Profile photo of Texylvanian
    Texylvanian
    Participant

    You know, I might just do that, but will have to clean the company proprietary information out of it first. As I mentioned above, we are really tiny, so not a lot of bandwidth, but that's something that I might be able to do in my ample free time. 😉

    Seriously, my solution can be easily normalized and cleaning it up and publishing it would help further cement my knowledge. I'll get approval from the higher ups (you know the "what you code is ours" agreement that everyone signs), and we'll get something up there, probably as a really basic web.config that somebody else could build on.

You must be logged in to reply to this topic.