Tuesday, December 02, 2014

Using Pester to validate DSC resources and configurations Part 2

Pester tests are like any other script. They grow and evolve over time. Here are a few more tests that I have testing my DSC resources and configurations that I recently added to my collection. 

Does every resource have a pester test?
This is probably one of the most important tests I have. Every resource should have a test, so why not test for that?

describe "DSCResources located in $PSScriptRoot\DSCResources" {

  foreach($Resource in $ResourceList)
  {
    context $Resource.name {

      it "Has a pester test" {

        ($Resource.fullname + "\*.test.ps1") | should exist
      }

If it is a standard resource, does it have the files it needs?
Each DSC resource needs to have two files in it. A *.psm1 file and a *.schema.mof file. I use the *.psm1 file as a quick way to identify standard resources differently than a composite resource. I know I will not ever reach a test condition that would cause once of these to fail, but I left it in place so I could change the logic later.

if(Test-Path ($Resource.fullname + "\$Resource.psm1"))
{

  it "Has a $Resource.schema.mof" {
                     
    ($Resource.fullname + "\$Resource.schema.mof") | should exist
  }
 
  it "Has a $Resource.psm1" {
                     
    ($Resource.fullname + "\$Resource.psm1") | should exist
  }

Does it pass Test-xDscSchema and Test-xDscResource tests?
I may as well test for these as part of my pester tests. They already validate a lot of things that are easy to overlook.

it "Passes Test-xDscSchema *.schema.mof" {
  Test-xDscSchema ($Resource.fullname + "\$Resource.schema.mof") | should be true
}
 
it "Passes Test-xDscResource" {
  Test-xDscResource $Resource.fullname | should be true
}

If it is a composite resource, does it have the required files?
A composite resource uses different files than a standard resource. It has a *.psd1 and a *.shema.psm1 that should exists. I don’t have any Test-xDSC functions for the composite resources so I add a few extra checks. I verify that the *.psd1 file references the *.psm1 and that the module does not throw any errors when dot sourcing it.

else
{
  it "Has a $Resource.schema.psm1" {
                     
    ($Resource.fullname + "\$Resource.schema.psm1") | should exist
  }
 
  it "Has a $Resource.psd1" {
                     
    ($Resource.fullname + "\$Resource.psd1") | should exist
  }
 
  it "Has a psd1 that loads the schema.psm1" {
 
    ($Resource.fullname + "\$Resource.psd1") | should contain "$Resource.schema.psm1"
  }
 
  it "dot-sourcing should not throw an error" {
 
    $path = ($Resource.fullname + "\$Resource.schema.psm1")
    { Invoke-expression (Get-Content $path -raw) } | should not throw
  }


I hope you find this examples useful. If you want to see more, take a look at part 1.

Tuesday, November 25, 2014

Setting HKey_Curent_User with a DSC resource

I built a fun new resource for managing registry settings. “DSC already has a resource for managing the registry” you say? This one sets values to user registry settings for all users.

    KevMar_UserRegistry DisableScreenSaver
    {
        ID        = "DisableScreenSaver"
        Key       = "HKEY_CURRENT_USER\Control Panel\Desktop"
        ValueName = "ScreenSaveActive"
        ValueData = "0"
    }

How cool is that? The built in DSC registry resource can only manage system settings. For servers this is all you really need. But if you have to manage user settings for some reason, forget about it. You need to use my resource to do it.

There are several limitations with my implementation to understand before we dive into how it works.

First is that this setting applies to all existing users and every new user once it is set. So if you remove this setting from future configurations instead of using the Ensure = "Absent" option, new users to the system will continue to get the setting. The good news is that using Ensure = “Absent” does stop this from applying to new users.

Second is that this sets the value only once per user. This kind of breaks the idea of DSC maintaining configuration drift. If this needs to get reapplied, there is a version attribute that must be used and incremented. Each user keeps track of what version of the setting they have applied. Increasing the version signals the user that something has changed and it needs to be set again. This is important if you are changing the ValueData  to something different.

Third these registry settings are only applied at user logon. I am using a method that hooks into the user logon process to apply the registry settings. I do not flag a reboot to DSC. I considered it but if you are starting to manage user settings, there can be a huge number of these in your configurations. Requiring a reboot for each one feels like a bit much. In my use case, I did not want the reboot. This is why making it as Absent can stop if from applying to any more users.


I’ll do a write up about how I did this in a future post. I used a Windows feature that is not very well known to most systems admins. I have it posted over at https://github.com/kmarquette/Powershell/tree/master/DSCModules/KevMar/DSCResources if you want to check it out.

Monday, November 24, 2014

Use Show-Command for a quick PowerShell GUI

We all love PowerShell and know how awesome it is. But not everyone we work with is as willing to drop to the shell as we are. The good news is there is a very easy way to give them the GUI they think they need. Write your advanced function like you already do and have them run it with Show-Command.

Show-Command Set-Service
This beautiful little command pops up and asks them for the information needed to run the command. Give it a try with Show-Command Set-Service

Not only are the required values marked, you even get a drop down box for some options. This works if you target *.ps1 files too.


As cool as this is, Show-Command gets better. Run it on its own and it gives you a list of all commands on the system. The filter makes it very easy to find what you are looking for. 

Thursday, November 20, 2014

$Using: scope in DSC script resources

If yo have spent any time working with DSc resources, you have found yourself needing to use the Script resource for some things.

Script Script_Resource
{
    GetScript = {}
    SetScript = {}
    TestScript = {}
    DependsOn = ""
    Credential = ""
}

It is easy enough to use. Define your Get, Set, and Test PowerShell commands and you are all set. Everything is good until you need to work with passing in variables into your script block. You will quickly find that it does not work like you would expect.

This command will always return false:

$path = "c:\windows\temp"
Script Script_Resource
{
    TestScript = {
        Test-Path $path
    }
}

Because $path is not defined within the scope of the TestScript to a value, the Test-Path will return false. Take a look at the mof file and you can see this. 

instance of MSFT_ScriptResource as $MSFT_ScriptResource1ref
{
 ResourceID = "[Script]Script_Resource";
 TestScript = "\n Test-Path '$path'\n ";
 SourceInfo = "::7::9::Script";
 ModuleName = "PSDesiredStateConfiguration";
 ModuleVersion = "1.0";

};


I have found two ways to deal with this issue. If you think about it, the TestScript is just a string that gets ran on the target node. If you look at the resource, TestScript is defined as a String.

$path = "c:\windows\temp"
Script Script_Resource
{
    TestScript ="Test-Path '$path'"
}


This works really well when the command is a very simple one line script. Take a look at the mof file now.

instance of MSFT_ScriptResource as $MSFT_ScriptResource1ref
{
 ResourceID = "[Script]Script_Resource";
 TestScript = "Test-Path 'c:\\windows\\temp'";
 SourceInfo = "::6::9::Script";
 ModuleName = "PSDesiredStateConfiguration";
 ModuleVersion = "1.0";

}; 

This could end up very messy if the script block gets more complicated. What if you have variables that you want to define in the script and use some from the parent scope. You end up escaping things with that horrible back tick. But there is a better way.

This is where the $using: scope comes to the rescue. As far as I can tell, this is undocumented for use in script resources. But using it in Invoke-Command script blocks will allow you to reference variables in the parent scope. It works for our script resource too.

$path = "c:\windows\temp"
Script Script_Resource
{
    TestScript = {
        Test-Path $using:path
    }

Now when we dive into the mof file, we can see just how this magic works. Our $path gets inserted into the script as part of the script. 

instance of MSFT_ScriptResource as $MSFT_ScriptResource1ref
{
 ResourceID = "[Script]Script_Resource";
 TestScript = "$path='c:\\windows\\temp'\n Test-Path $path\n ";
 SourceInfo = "::7::9::Script";
 ModuleName = "PSDesiredStateConfiguration";
 ModuleVersion = "1.0";
};

The $using: scope is something I often overlook but this will be a very handy way to use it.

One final note about my examples. I did trim them down to minimize the code. If you want to recreate my tests, you will need to have the SetScript and GetScript properties defined for each script block.

Monday, November 03, 2014

Using Pester to validate DSC resources and configurations

The more I use Pester, the more I like it. I found some ways to leverage it in validating my use of DSC Resources and configurations. Here are some samples to give you an idea of what I am talking about.

Are my resources loaded?
Often I will create a new resource thinking it will work only to not have it loaded for some reason. At the moment all my resources are in the same module. So I have this test to use my folder structure to test for a loaded resource.

$LoadedResources = Get-DscResource

Describe “DSCResources located in $PSScriptRoot\DSCResources" {
    $ResourceList = Get-ChildItem "$PSScriptRoot\DSCResources"
   
    Foreach($Resource in $ResourceList){
        It "$Resource Is Loaded [Dynamic]" {
            $LoadedResources |
                Where-Object{$_.name -eq $Resource} |
                Should Not BeNullOrEmpty
        }
    }
}


Can  my resource generate a mof file?
The next thing I want to know is if it will generate a mof file. I create a test like this for every DSC resource. I use the TestDrive: location to manage temporary files.

Describe "Firewall"{
    It "Creates a mof file"{
        configuration DSCTest{
            Import-DscResource -modulename MyModule  
            Node Localhost{
               Firewall SampleConfig{
                    State = "ON"
               }
            }
        }
        DSCTest -OutputPath Testdrive:\dsc
        "TestDrive:\dsc\localhost.mof" | Should Exist
    }

Do my Test and Get functions work?
For some of my resources, I even test the Get and Test functions inside the module. I first have to rename the script so I can load it. Also notice I use a Mock function to keep the Export-Module from throwing errors.


$here = Split-Path -Parent $MyInvocation.MyCommand.Path

Describe "Firewall"{
    Copy-Item "$here\Firewall.psm1" TestDrive:\script.ps1
    Mock Export-ModuleMember {return $true}

    . "TestDrive:\script.ps1"
    It "Test-TargetResource returns true or false" {
        Test-TargetResource -state "ON" |
            Should Not BeNullOrEmpty
    }

    It "Get-TargetResource returns State = on or off" {
        (Get-TargetResource -state "ON").state |
            Should Match "on|off"
    }
}

Edit: I added a part 2.

Reading the Windows PowerShell Language Specification Version 3.0

Anytime I spend a good deal of time with a technology, I eventually track down the core documentation. The real technical stuff that shows you everything. I just rediscovered the Windows PowerShell Language Specification Version 3.0. It would be nice to have one for PowerShell 4.0, but I did not see it yet. I rediscovered a few things that I either missed before or I was not ready to understand them yet. Here are some hidden gems that I would like to share with you.

Wildcard attributes
We use these all the time for partial matches. The cool thing is that we can use them in file paths to skip over folders that may have any name. Let me show you how we can use this to fix an old problem of mine.

Our home folders are also the users my documents folder. Every time a user logs in, they create a desktop.ini file in that location. It does this cool trick of renaming the folder to say "My Documents". This is nice, unless you are an admin looking a list of 500 folders called "My Documents". Here is a quick fix to delete all of those files.

Get-ChildItem d:\profile\*\desktop.ini | Remove-Item -Force

ForEach-Object -Member
I can't say that I have many uses for this one, but I think it is interesting. The ForEach-Object has a -MemberName property. If you provide it an objects function name, it will call it for each object. Here is one good example of using it to uninstall software.

Get-WMIObject Win32_Product | Where-Object name -match Java | ForEach-Object -MemberName Uninstall

Or we can shorthand this same command even more like this:

gwmi win32_product | ? name -match Java | % uninstall

$PsBoundParameters
This variable contains a dictionary of all the parameters that were passed to the function or script. The cool thing is that you can splat these to another function. This would be great when you are creating a wrapper around a function that takes lots of parameters. I don;t have a good example off hand, but I know I have code out there that would be much cleaner with this


Thursday, July 24, 2014

Loading .Net Assemblys into Powershell

I have some functionality written in C# that I want to use in PowerShell. I compiled the code I wanted into a dll and copied it to my target system. Then I used this snip of code to load and use it:

[System.Reflection.Assembly]::LoadFrom(".\myProject.dll")
$myObject = New-Object myNamespace.myClass
$myObject.MyFunction()

If my functions return objects, I can use them just like I would any other object in PowerShell. I just found this as another way to use the .Net Framework.