0

PowerShell – Creating DSC module – Part 3

logo-powershell-520x245We continue today with our DSC module creation. If you have been following this blog in recent days you might have noticed me posting information about good practices and idea I had to create this module. To keep up with the series you might want to visit :

  1. Part 1
  2. Part 2
  3. DSC Best practices

Since we can say we get smarter everyday its time to do …

Recap :

  • First of all we need to re-think the whole purpose of creating 2 resources for appPool management (here I refer to managing appPool defaults and remaining appPools). Why should we reconsider ? Well simply because with my original approach I’m unnecessarly duplicating data. As every application pool has the same properties as the default one … whats the point of having 2 resources ? well there isnt – so we are going to change that
  • Another one is appropiate naming of script responsible for naming resource. So lets go and start off with…

Structuring files the right way

By following point 21 from this recently mentioned DSC checklist we will be putting script which creates our resources into folder

C:\Program Files\WindowsPowerShell\Modules\cWebAdmin\DSCResources\RafPe_cWebAppPool\ResourceDesignerScripts

The file will be named accordingly to best practices – so using Generate<ResourceName>Schema.ps1 . So I have ended up having GenerateCwebAppPoolSchema.ps1

So we got this sorted. I have cleaned up GitHub project from unnecessary files and now it will only contain resource for cWebAppPool

 

Creating DSC resource

So now after all of this excercises we can go ahead and create our resource. At this stage 🙂 there is no guaratee that it will all work out of the box 😀 but we will do our best.  You think probably that you can get the coffee before we finish…. well not really 😀 I have prepared the file already for you so by just copying the code below we will be able to get our resource in no time (up to date version of this file in as usual on GiHub – code showed here is for learning purposes more )

   $Ensure                         = New-xDscResourceProperty -Name Ensure -Type String -Attribute Write -ValidateSet 'Present', 'Absent'
   $name                           = New-xDscResourceProperty –Name Name –Type String –Attribute Key		
   $queueLength                    = New-xDscResourceProperty –Name queueLength –Type String –Attribute Write		
   $autoStart                      = New-xDscResourceProperty –Name autoStart –Type String –Attribute Write
   $enable32BitAppOnWin64          = New-xDscResourceProperty –Name enable32BitAppOnWin64 –Type string –Attribute Write -ValidateSet 'true','false'
   $managedRuntimeVersion          = New-xDscResourceProperty –Name managedRuntimeVersion –Type string –Attribute Write -ValidateSet 'v4.0','v2.0',''
   $managedRuntimeLoader           = New-xDscResourceProperty –Name managedRuntimeLoader –Type string –Attribute Write
   $enableConfigurationOverride    = New-xDscResourceProperty –Name enableConfigurationOverride –Type string –Attribute Write  -ValidateSet 'true','false'
   $managedPipelineMode            = New-xDscResourceProperty –Name managedPipelineMode –Type string –Attribute Write -ValidateSet 'Integrated','Classic'
   $CLRConfigFile                  = New-xDscResourceProperty –Name CLRConfigFile –Type string –Attribute Write
   $passAnonymousToken             = New-xDscResourceProperty –Name passAnonymousToken –Type string –Attribute Write -ValidateSet 'true','false'
   $startMode                      = New-xDscResourceProperty –Name startMode –Type string –Attribute Write -ValidateSet 'AlwaysRunning','OnDemand'
   $identityType                   = New-xDscResourceProperty –Name identityType –Type string –Attribute Write -ValidateSet 'ApplicationPoolIdentity','LocalSystem','LocalService','NetworkService','SpecificUser'
   $userName                       = New-xDscResourceProperty –Name userName –Type string –Attribute Write
   $password                       = New-xDscResourceProperty –Name Password –Type PSCredential –Attribute Write
   $loadUserProfile                = New-xDscResourceProperty –Name loadUserProfile –Type string –Attribute Write -ValidateSet 'true','false'
   $setProfileEnvironment          = New-xDscResourceProperty –Name setProfileEnvironment –Type string –Attribute Write -ValidateSet 'true','false'
   $logonType                      = New-xDscResourceProperty –Name logonType –Type string –Attribute Write -ValidateSet 'LogonBatch','LogonService'
   $manualGroupMembership          = New-xDscResourceProperty –Name manualGroupMembership –Type string –Attribute Write  -ValidateSet 'true','false'
   $idleTimeout                    = New-xDscResourceProperty –Name idleTimeout –Type string –Attribute Write
   $idleTimeoutAction              = New-xDscResourceProperty –Name idleTimeoutAction –Type string –Attribute Write -ValidateSet 'Terminate','Suspend'
   $maxProcesses                   = New-xDscResourceProperty –Name maxProcesses –Type string –Attribute Write
   $shutdownTimeLimit              = New-xDscResourceProperty –Name shutdownTimeLimit –Type string –Attribute Write
   $startupTimeLimit               = New-xDscResourceProperty –Name startupTimeLimit –Type string –Attribute Write
   $pingingEnabled                 = New-xDscResourceProperty –Name pingingEnabled –Type string –Attribute Write -ValidateSet 'true','false'
   $pingInterval                   = New-xDscResourceProperty –Name pingInterval –Type string –Attribute Write
   $pingResponseTime               = New-xDscResourceProperty –Name pingResponseTime –Type string –Attribute Write
   $disallowOverlappingRotation    = New-xDscResourceProperty –Name disallowOverlappingRotation –Type string –Attribute Write -ValidateSet 'true','false'
   $disallowRotationOnConfigChange = New-xDscResourceProperty –Name disallowRotationOnConfigChange –Type string –Attribute Write -ValidateSet 'true','false'
   $logEventOnRecycle              = New-xDscResourceProperty –Name logEventOnRecycle –Type string –Attribute Write
   $memory                         = New-xDscResourceProperty –Name memory –Type string –Attribute Write
   $privateMemory                  = New-xDscResourceProperty –Name privateMemory –Type string –Attribute Write
   $requests                       = New-xDscResourceProperty –Name requests –Type string –Attribute Write
   $time                           = New-xDscResourceProperty –Name time –Type String –Attribute Write
   $schedule                       = New-xDscResourceProperty –Name schedule –Type Hashtable[] –Attribute Write
   $loadBalancerCapabilities       = New-xDscResourceProperty –Name loadBalancerCapabilities –Type string –Attribute Write -ValidateSet 'HttpLevel','TcpLevel'
   $orphanWorkerProcess            = New-xDscResourceProperty –Name orphanWorkerProcess –Type string –Attribute Write -ValidateSet 'true','false'
   $orphanActionExe                = New-xDscResourceProperty –Name orphanActionExe –Type string –Attribute Write
   $orphanActionParams             = New-xDscResourceProperty –Name orphanActionParams –Type string –Attribute Write
   $rapidFailProtection            = New-xDscResourceProperty –Name rapidFailProtection –Type string –Attribute Write -ValidateSet 'true','false'
   $rapidFailProtectionInterval    = New-xDscResourceProperty –Name rapidFailProtectionInterval –Type string –Attribute Write
   $rapidFailProtectionMaxCrashes  = New-xDscResourceProperty –Name rapidFailProtectionMaxCrashes –Type string –Attribute Write
   $autoShutdownExe                = New-xDscResourceProperty –Name autoShutdownExe –Type string –Attribute Write
   $autoShutdownParams             = New-xDscResourceProperty –Name autoShutdownParams –Type string –Attribute Write
   $limit                          = New-xDscResourceProperty –Name limit –Type string –Attribute Write
   $action                         = New-xDscResourceProperty –Name action –Type string –Attribute Write -ValidateSet 'NoAcion','KillW3wp','Throttle','ThrottleUnderLoad'
   $resetInterval                  = New-xDscResourceProperty –Name resetInterval –Type string –Attribute Write
   $smpAffinitized                 = New-xDscResourceProperty –Name smpAffinitized –Type string –Attribute Write -ValidateSet 'true','false'
   $smpProcessorAffinityMask       = New-xDscResourceProperty –Name smpProcessorAffinityMask –Type string –Attribute Write
   $smpProcessorAffinityMask2      = New-xDscResourceProperty –Name smpProcessorAffinityMask2 –Type string –Attribute Write
   $processorGroup                 = New-xDscResourceProperty –Name processorGroup –Type string –Attribute Write
   $numaNodeAssignment             = New-xDscResourceProperty –Name numaNodeAssignment –Type string –Attribute Write -ValidateSet 'MostAvailableMemory','WindowsScheduling'
   $numaNodeAffinityMode           = New-xDscResourceProperty –Name numaNodeAffinityMode –Type string –Attribute Write -ValidateSet 'Soft','Hard'

#array to hold our properties
$xDscProperties [email protected](
       $Ensure
       $name,
       $queueLength,
       $autoStart,
       $enable32BitAppOnWin64,
       $managedRuntimeVersion,
       $managedRuntimeLoader,
       $enableConfigurationOverride,
       $managedPipelineMode,
       $CLRConfigFile,
       $passAnonymousToken,
       $startMode,
       $identityType,
       $userName,
       $password,
       $loadUserProfile,
       $setProfileEnvironment,
       $logonType,
       $manualGroupMembership,
       $idleTimeout,
       $idleTimeoutAction,
       $maxProcesses,
       $shutdownTimeLimit,
       $startupTimeLimit,
       $pingingEnabled,
       $pingInterval,
       $pingResponseTime,
       $disallowOverlappingRotation,
       $disallowRotationOnConfigChange,
       $logEventOnRecycle,
       $memory,
       $privateMemory,
       $requests,
       $time,
       $schedule,
       $loadBalancerCapabilities,
       $orphanWorkerProcess,
       $orphanActionExe,
       $orphanActionParams,
       $rapidFailProtection,
       $rapidFailProtectionInterval,
       $rapidFailProtectionMaxCrashes,
       $autoShutdownExe,
       $autoShutdownParams,
       $limit,
       $action,
       $resetInterval,
       $smpAffinitized,
       $smpProcessorAffinityMask,
       $smpProcessorAffinityMask2,
       $processorGroup,
       $numaNodeAssignment,
       $numaNodeAffinityMode      
    )

# Create resource that will be creating our application pool 
New-xDscResource -Name RafPe_cWebAppPool`
                 -FriendlyName cWebAppPool`
                 -ModuleName cWebAdmin`
                 -Path 'C:\Program Files\WindowsPowerShell\Modules' `
                 -Property $xDscProperties -Verbose

 

So what happened here ? We have created a lot of DSC resource properties and then used array to hold those values. Why array ? Well because then its simple to pass it into the command which has created the resource for us. New-xDscResource 

You may noticed that I have used RafPe_cWebAppPool as the name. This is because I’m not the only one creating names and they for obvious reasons cannot be the same. Therefore further as parameter we have friendly name which I set to be cWebAppPool. Next is quite important as it is our module name which I have decided to be cWebAdmin as purpose of this module is to manage not only app Pool but much more on IIS webservers . Lastly its path (which I use the default ones for modules ) and finally our array of properties for creation.

For purposes of better visibility I have made sure there is the verbose switch added.

Once you execute this you should see output similar to the following :

2015-08-13_22h41_02

 

 

Prep work for resource functions

Now comes the most interesting part. Remember the important trio of functions I mentioned ? Get/Set/Test ? This will be the time to start coding them to do the really heavy lifting for us.

Now since I really like to simplify my life we will use a wrapper function to get and set settings in AppPool. Reason for doing this will be clearly visible later when we cut down the number of unnecessary code really significantly.

As usual the most up to date function you will find on GitHub

# Function which allows for quick set/get of app pool property - make sure you have the newest one :)
# available @  https://gist.github.com/RafPe/77d2ff28f7a0014bf0f1
function Invoke-AppPoolSetting
{

    param
    (    
        [string]$appPoolName,
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $propertyName,
        [Parameter(Mandatory=$true)]
        [ValidateSet('get','set')]
        [string]$action,
        $value                
    )

    #Mapping of namespace (works on IIS 8.5)
    $NamespaceMapping = @{
       name                        = 'system.applicationHost/applicationPools/{0}'
       queueLength                 = 'system.applicationHost/applicationPools/{0}';
       autoStart                   = 'system.applicationHost/applicationPools/{0}';
       enable32BitAppOnWin64       = 'system.applicationHost/applicationPools/{0}';
       managedRuntimeVersion       = 'system.applicationHost/applicationPools/{0}';
       managedRuntimeLoader        = 'system.applicationHost/applicationPools/{0}';
       enableConfigurationOverride = 'system.applicationHost/applicationPools/{0}';
       managedPipelineMode         = 'system.applicationHost/applicationPools/{0}';
       CLRConfigFile               = 'system.applicationHost/applicationPools/{0}';
       passAnonymousToken          = 'system.applicationHost/applicationPools/{0}';
       startMode                   = 'system.applicationHost/applicationPools/{0}';
       identityType          = 'system.applicationHost/applicationPools/{0}/processModel';
       userName              = 'system.applicationHost/applicationPools/{0}/processModel';
       password              = 'system.applicationHost/applicationPools/{0}/processModel';
       loadUserProfile       = 'system.applicationHost/applicationPools/{0}/processModel';
       setProfileEnvironment = 'system.applicationHost/applicationPools/{0}/processModel';
       logonType             = 'system.applicationHost/applicationPools/{0}/processModel';
       manualGroupMembership = 'system.applicationHost/applicationPools/{0}/processModel';
       idleTimeout           = 'system.applicationHost/applicationPools/{0}/processModel';
       idleTimeoutAction     = 'system.applicationHost/applicationPools/{0}/processModel';
       maxProcesses          = 'system.applicationHost/applicationPools/{0}/processModel';
       shutdownTimeLimit     = 'system.applicationHost/applicationPools/{0}/processModel';
       startupTimeLimit      = 'system.applicationHost/applicationPools/{0}/processModel';
       pingingEnabled        = 'system.applicationHost/applicationPools/{0}/processModel';
       pingInterval          = 'system.applicationHost/applicationPools/{0}/processModel';
       pingResponseTime      = 'system.applicationHost/applicationPools/{0}/processModel';
       disallowOverlappingRotation    = 'system.applicationHost/applicationPools/{0}/recycling';
       disallowRotationOnConfigChange = 'system.applicationHost/applicationPools/{0}/recycling';
       logEventOnRecycle              = 'system.applicationHost/applicationPools/{0}/recycling';
       memory                         = 'system.applicationHost/applicationPools/{0}/recycling/periodicRestart';
       privateMemory                  = 'system.applicationHost/applicationPools/{0}/recycling/periodicRestart';
       requests                       = 'system.applicationHost/applicationPools/{0}/recycling/periodicRestart';
       time                           = 'system.applicationHost/applicationPools/{0}/recycling/periodicRestart';
       schedule                       = 'system.applicationHost/applicationPools/{0}/recycling/periodicRestart/schedule';
       loadBalancerCapabilities      = 'system.applicationHost/applicationPools/{0}/failure';
       orphanWorkerProcess           = 'system.applicationHost/applicationPools/{0}/failure';
       orphanActionExe               = 'system.applicationHost/applicationPools/{0}/failure';
       orphanActionParams            = 'system.applicationHost/applicationPools/{0}/failure';
       rapidFailProtection           = 'system.applicationHost/applicationPools/{0}/failure';
       rapidFailProtectionInterval   = 'system.applicationHost/applicationPools/{0}/failure';
       rapidFailProtectionMaxCrashes = 'system.applicationHost/applicationPools/{0}/failure';
       autoShutdownExe               = 'system.applicationHost/applicationPools/{0}/failure';
       autoShutdownParams            = 'system.applicationHost/applicationPools/{0}/failure';
       limit                     = 'system.applicationHost/applicationPools/{0}/cpu';
       action                    = 'system.applicationHost/applicationPools/{0}/cpu';
       resetInterval             = 'system.applicationHost/applicationPools/{0}/cpu';
       smpAffinitized            = 'system.applicationHost/applicationPools/{0}/cpu';
       smpProcessorAffinityMask  = 'system.applicationHost/applicationPools/{0}/cpu';
       smpProcessorAffinityMask2 = 'system.applicationHost/applicationPools/{0}/cpu';
       processorGroup            = 'system.applicationHost/applicationPools/{0}/cpu';
       numaNodeAssignment        = 'system.applicationHost/applicationPools/{0}/cpu';
       numaNodeAffinityMode      = 'system.applicationHost/applicationPools/{0}/cpu';
    }

    # Create target app pool name 
    if( [string]::IsNullOrEmpty($appPoolName) -or $appPoolName -eq 'applicationPoolDefaults' ) { $targetAppName = 'applicationPoolDefaults' } else { $targetAppName = [string]::Format("add[@name='{0}']",$appPoolName ) }
    
    switch ($action)
    {
      'get'        { 

                        Write-Debug "using $targetAppName as target name"
                        # gets value
                        
                        try
                        {
                          Write-Debug "Requesting property $propertyName value for appPool $appPoolName"
                          $res =  (Get-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST'  -filter $( $NamespaceMapping[ $propertyName ] -f $targetAppName ) -name $propertyName)
                            
                          Write-Debug "property is type of $($res.GetType().Name)"

                          #We need to distinguish from simple value type to complex value types
                          if ( $res.GetType().Name -eq 'ConfigurationAttribute')
                          {

                            return $res.Value.ToString()
                          }
                          else
                          {

                           
                            return $res.ToString()
                          }

                        }
                        catch
                        {
                            Throw "Could not retrieve property $propertyName for appPool $Name"
                        }
                        
                        
                   }
      
      'set'        { 
                       
                       if($null -eq $value)
                       {
                            Throw 'Please ensure that value is specified for set and is not NULL'
                       }

                        try
                        {
                          Write-Debug "Setting  property $propertyName with value $value for appPool $Name"
                          Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST'  -filter $( $NamespaceMapping[ $propertyName ] -f $targetAppName ) -name $propertyName -Value $value
                        
                        }
                        catch
                        {
                            Throw "Could not set property $propertyName with value $value for appPool $Name"
                        }

                       

                   }
    }
}

 

What does this allows me to do ? Well quite simple – in one cmdlet based on dynamic paramater name/value pair (or just parameter name) I can get or set a value for application pool! I think this is quite useful. So how this cmdlet is being used ?

Invoke-AppPoolSetting -propertyName disallowRotationOnConfigChange -action get;

The above quite easily gets me exactly the property I’m after. And now also the setting of property is quite intuitive. You just need to flip the correct action parameter and voilla – you are done for settign and getting in professional way.

 

Get-Resource

Its time to dive into functons that are responsible for actual heavy lifting of the resources. We will first focus on the Get function. It is quite simple as it just gets all resource properties which we have defined while creating this resource.  As usual the most up to date function wll be available on GitHub

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name
    )

    #Remove debug from params
    $psboundparameters.Remove('Debug') | Out-Null
    $psboundparameters.Remove('Verbose') | Out-Null

    #TODO - schedule fix up 
    $psboundparameters.Remove('schedule') | Out-Null

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    Write-Verbose 'Checking for app pool specified credentials'
    $appPoolUserName = Invoke-AppPoolSetting -propertyName userName -action get

    if($appPoolUserName)
    {
                Write-Verbose 'app pool has specific identity - creating PSCredential object'

                $AppPoolPassword = Invoke-AppPoolSetting -propertyName password -action get | ConvertTo-SecureString -AsPlainText -Force
                $AppPoolCred     = new-object -typename System.Management.Automation.PSCredential -argumentlist $appPoolUserName,$AppPoolPassword
    }
    else
    {
                Write-Verbose 'app pool does not have specific identity - nothing to do here'
                $AppPoolCred =$null
    }

    
    $returnValue = @{
        Name                          = Invoke-AppPoolSetting -propertyName Name -action get;
        queueLength                   = Invoke-AppPoolSetting -propertyName queueLength -action get;
        autoStart                     = Invoke-AppPoolSetting -propertyName autoStart -action get;
        enable32BitAppOnWin64         = Invoke-AppPoolSetting -propertyName enable32BitAppOnWin64 -action get;
        managedRuntimeVersion         = Invoke-AppPoolSetting -propertyName managedRuntimeVersion -action get;
        managedRuntimeLoader          = Invoke-AppPoolSetting -propertyName managedRuntimeLoader -action get;
        enableConfigurationOverride   = Invoke-AppPoolSetting -propertyName enableConfigurationOverride -action get;
        managedPipelineMode           = Invoke-AppPoolSetting -propertyName managedPipelineMode -action get;
        CLRConfigFile                 = Invoke-AppPoolSetting -propertyName CLRConfigFile -action get;
        passAnonymousToken            = Invoke-AppPoolSetting -propertyName passAnonymousToken -action get;
        startMode                     = Invoke-AppPoolSetting -propertyName startMode -action get;
        identityType                  = Invoke-AppPoolSetting -propertyName identityType -action get;
        userName                      = $appPoolUserName;
        Password                      = $AppPoolPassword;
        loadUserProfile               = Invoke-AppPoolSetting -propertyName loadUserProfile -action get;
        setProfileEnvironment         = Invoke-AppPoolSetting -propertyName setProfileEnvironment -action get;
        logonType                     = Invoke-AppPoolSetting -propertyName logonType -action get;
        manualGroupMembership         = Invoke-AppPoolSetting -propertyName manualGroupMembership -action get;
        idleTimeout                   = Invoke-AppPoolSetting -propertyName idleTimeout -action get;
        idleTimeoutAction             = Invoke-AppPoolSetting -propertyName idleTimeoutAction -action get;
        maxProcesses                  = Invoke-AppPoolSetting -propertyName maxProcesses -action get;
        shutdownTimeLimit             = Invoke-AppPoolSetting -propertyName shutdownTimeLimit -action get;
        startupTimeLimit              = Invoke-AppPoolSetting -propertyName startupTimeLimit -action get;
        pingingEnabled                = Invoke-AppPoolSetting -propertyName pingingEnabled -action get;
        pingInterval                  = Invoke-AppPoolSetting -propertyName pingInterval -action get;
        pingResponseTime              = Invoke-AppPoolSetting -propertyName pingResponseTime -action get;
        disallowOverlappingRotation   = Invoke-AppPoolSetting -propertyName disallowOverlappingRotation -action get;
        disallowRotationOnConfigChange = Invoke-AppPoolSetting -propertyName disallowRotationOnConfigChange -action get;
        logEventOnRecycle             = Invoke-AppPoolSetting -propertyName logEventOnRecycle -action get;
        memory                        = Invoke-AppPoolSetting -propertyName memory -action get;
        privateMemory                 = Invoke-AppPoolSetting -propertyName privateMemory -action get;
        requests                      = Invoke-AppPoolSetting -propertyName requests -action get;
        time                          = Invoke-AppPoolSetting -propertyName time -action get;
        schedule                      = '';# TODO - investigate schedule setup Invoke-AppPoolSetting -propertyName schedule -action get;
        loadBalancerCapabilities      = Invoke-AppPoolSetting -propertyName loadBalancerCapabilities -action get;
        orphanWorkerProcess           = Invoke-AppPoolSetting -propertyName orphanWorkerProcess -action get;
        orphanActionExe               = Invoke-AppPoolSetting -propertyName orphanActionExe -action get;
        orphanActionParams            = Invoke-AppPoolSetting -propertyName orphanActionParams -action get;
        rapidFailProtection           = Invoke-AppPoolSetting -propertyName rapidFailProtection -action get;
        rapidFailProtectionInterval   = Invoke-AppPoolSetting -propertyName rapidFailProtectionInterval -action get;
        rapidFailProtectionMaxCrashes = Invoke-AppPoolSetting -propertyName rapidFailProtectionMaxCrashes -action get;
        autoShutdownExe               = Invoke-AppPoolSetting -propertyName autoShutdownExe -action get;
        autoShutdownParams            = Invoke-AppPoolSetting -propertyName autoShutdownParams -action get;
        limit                         = Invoke-AppPoolSetting -propertyName limit -action get;
        action                        = Invoke-AppPoolSetting -propertyName action -action get;
        resetInterval                 = Invoke-AppPoolSetting -propertyName resetInterval -action get;
        smpAffinitized                = Invoke-AppPoolSetting -propertyName smpAffinitized -action get;
        smpProcessorAffinityMask      = Invoke-AppPoolSetting -propertyName smpProcessorAffinityMask -action get;
        smpProcessorAffinityMask2     = Invoke-AppPoolSetting -propertyName smpProcessorAffinityMask2 -action get;
        processorGroup                = Invoke-AppPoolSetting -propertyName processorGroup -action get;
        numaNodeAssignment            = Invoke-AppPoolSetting -propertyName numaNodeAssignment -action get;
        numaNodeAffinityMode          = Invoke-AppPoolSetting -propertyName numaNodeAffinityMode -action get;
    }

    $returnValue

}

 

Test-resource

This function is as the name says 😀 responsible for testing property/value So without any further delays here is the function

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [System.String]
        $queueLength,

        [System.String]
        $autoStart,

        [ValidateSet('true','false')]
        [System.String]
        $enable32BitAppOnWin64,

        [ValidateSet('v4.0','v2.0','')]
        [System.String]
        $managedRuntimeVersion,

        [System.String]
        $managedRuntimeLoader,

        [ValidateSet('true','false')]
        [System.String]
        $enableConfigurationOverride,

        [ValidateSet('Integrated','Classic')]
        [System.String]
        $managedPipelineMode,

        [System.String]
        $CLRConfigFile,

        [ValidateSet('true','false')]
        [System.String]
        $passAnonymousToken,

        [ValidateSet('AlwaysRunning','OnDemand')]
        [System.String]
        $startMode,

        [ValidateSet('ApplicationPoolIdentity','LocalSystem','LocalService','NetworkService','SpecificUser')]
        [System.String]
        $identityType,

        [System.String]
        $userName,

        [System.Management.Automation.PSCredential]
        $Password,

        [ValidateSet('true','false')]
        [System.String]
        $loadUserProfile,

        [ValidateSet('true','false')]
        [System.String]
        $setProfileEnvironment,

        [ValidateSet('LogonBatch','LogonService')]
        [System.String]
        $logonType,

        [ValidateSet('true','false')]
        [System.String]
        $manualGroupMembership,

        [System.String]
        $idleTimeout,

        [ValidateSet('Terminate','Suspend')]
        [System.String]
        $idleTimeoutAction,

        [System.String]
        $maxProcesses,

        [System.String]
        $shutdownTimeLimit,

        [System.String]
        $startupTimeLimit,

        [ValidateSet('true','false')]
        [System.String]
        $pingingEnabled,

        [System.String]
        $pingInterval,

        [System.String]
        $pingResponseTime,

        [ValidateSet('true','false')]
        [System.String]
        $disallowOverlappingRotation,

        [ValidateSet('true','false')]
        [System.String]
        $disallowRotationOnConfigChange,

        [System.String]
        $logEventOnRecycle,

        [System.String]
        $memory,

        [System.String]
        $privateMemory,

        [System.String]
        $requests,

        [System.String]
        $time,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        $schedule,

        [ValidateSet('HttpLevel','TcpLevel')]
        [System.String]
        $loadBalancerCapabilities,

        [ValidateSet('true','false')]
        [System.String]
        $orphanWorkerProcess,

        [System.String]
        $orphanActionExe,

        [System.String]
        $orphanActionParams,

        [ValidateSet('true','false')]
        [System.String]
        $rapidFailProtection,

        [System.String]
        $rapidFailProtectionInterval,

        [System.String]
        $rapidFailProtectionMaxCrashes,

        [System.String]
        $autoShutdownExe,

        [System.String]
        $autoShutdownParams,

        [System.String]
        $limit,

        [ValidateSet('NoAcion','KillW3wp','Throttle','ThrottleUnderLoad')]
        [System.String]
        $action,

        [System.String]
        $resetInterval,

        [ValidateSet('true','false')]
        [System.String]
        $smpAffinitized,

        [System.String]
        $smpProcessorAffinityMask,

        [System.String]
        $smpProcessorAffinityMask2,

        [System.String]
        $processorGroup,

        [ValidateSet('MostAvailableMemory','WindowsScheduling')]
        [System.String]
        $numaNodeAssignment,

        [ValidateSet('Soft','Hard')]
        [System.String]
        $numaNodeAffinityMode
    )

    #Remove debug from params
    $psboundparameters.Remove('Debug') | Out-Null
    $psboundparameters.Remove('Verbose') | Out-Null

    #TODO - schedule fix up 
    $psboundparameters.Remove('schedule') | Out-Null

    $DesiredConfigurationMatch = $true

    # Check if WebAdministration module is present for IIS cmdlets
    if(!(Get-Module -ListAvailable -Name WebAdministration))
    {
        Throw 'Please ensure that WebAdministration module is installed.'
    }

    #Enumrate all params and info if necessary 
    foreach($psbp in $PSBoundParameters.GetEnumerator())
    {
          Write-Debug "xChecking value for $($psbp.Key)"

          $currValue = (Invoke-AppPoolSetting -propertyName $psbp.Key -action get)
          Write-Debug "Current value type :  $($currValue.GetType().Name)"
          Write-Debug "DSC type :  $($($psbp.Value).GetType().Name)"
          
          # We get current status and compare it with desired state  
          if( $currValue -ne $psbp.Value )
          {
                Write-Debug "Value for $( $psbp.Key ) [ $currValue ]  does not match the desired state [$( $psbp.Value )]"

                $DesiredConfigurationMatch = $false
          }
          else
          {
                 Write-Debug "Value for $( $psbp.Key ) [ $currValue ]  does match the desired state [$( $psbp.Value )]"
          }

    }

    return $DesiredConfigurationMatch
}

 

There are couple of points in this function wich are worth a bit of explanation. As I have seen several implementation of this function where suddenly you have ~ 150 conditional statements i.e. (if ($x) {} else {} ) and so on … so on and so on….. come on! Of course you can do better and optimize your code against redundant repetitions. Think always here about the moing parts … what is shared … what is common … what can you use …. 😀

So what can we use here ? Well something like $PsBoundParameters. If you would ask what are those asking the rigth places says … “It’s a hashtable containing the parameters that were bound and what values were bound to them” (source of this info )

We start by etablishing iteration of all available parameters

foreach($psbp in $PSBoundParameters.GetEnumerator())
{
    // ......

This will give us all parameters that have been passed to this function. Thats already great first step. So since this is testing function we need to start testing parameters passed . First we get current value using our customized function which we discussed before

$currValue = (Invoke-AppPoolSetting -propertyName $psbp.Key -action get)

This will retrieve current value for our specified setting. $psbp.Key will be holding name of parameter passes i.e. autostart , name etc.

Having that value we will compare it with our desired state. We do that by comparing $currValue with $psbp.Value (which is value passed as parameter)

# We get current status and compare it with desired state  
          if( $currValue -ne $psbp.Value )
          {
                Write-Debug "Value for $( $psbp.Key ) [ $currValue ]  does not match the desired state [$( $psbp.Value )]"

                $DesiredConfigurationMatch = $false
          }
          else
          {
                 Write-Debug "Value for $( $psbp.Key ) [ $currValue ]  does match the desired state [$( $psbp.Value )]"
          }

The last thing that is left is to return value which determines if we need to act. This is done by returning $DesiredConfigurationMatch

Ok so not much left to first success 😀 The remaining one is the one that does the work for us when we have drift from required configuration

Set-resource

We will apply similar approach as with our function above. Code looks following

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [System.String]
        $queueLength,

        [System.String]
        $autoStart,

        [ValidateSet('true','false')]
        [System.String]
        $enable32BitAppOnWin64,

        [ValidateSet('v4.0','v2.0','')]
        [System.String]
        $managedRuntimeVersion,

        [System.String]
        $managedRuntimeLoader,

        [ValidateSet('true','false')]
        [System.String]
        $enableConfigurationOverride,

        [ValidateSet('Integrated','Classic')]
        [System.String]
        $managedPipelineMode,

        [System.String]
        $CLRConfigFile,

        [ValidateSet('true','false')]
        [System.String]
        $passAnonymousToken,

        [ValidateSet('AlwaysRunning','OnDemand')]
        [System.String]
        $startMode,

        [ValidateSet('ApplicationPoolIdentity','LocalSystem','LocalService','NetworkService','SpecificUser')]
        [System.String]
        $identityType,

        [System.String]
        $userName,

        [System.Management.Automation.PSCredential]
        $Password,

        [ValidateSet('true','false')]
        [System.String]
        $loadUserProfile,

        [ValidateSet('true','false')]
        [System.String]
        $setProfileEnvironment,

        [ValidateSet('LogonBatch','LogonService')]
        [System.String]
        $logonType,

        [ValidateSet('true','false')]
        [System.String]
        $manualGroupMembership,

        [System.String]
        $idleTimeout,

        [ValidateSet('Terminate','Suspend')]
        [System.String]
        $idleTimeoutAction,

        [System.String]
        $maxProcesses,

        [System.String]
        $shutdownTimeLimit,

        [System.String]
        $startupTimeLimit,

        [ValidateSet('true','false')]
        [System.String]
        $pingingEnabled,

        [System.String]
        $pingInterval,

        [System.String]
        $pingResponseTime,

        [ValidateSet('true','false')]
        [System.String]
        $disallowOverlappingRotation,

        [ValidateSet('true','false')]
        [System.String]
        $disallowRotationOnConfigChange,

        [System.String]
        $logEventOnRecycle,

        [System.String]
        $memory,

        [System.String]
        $privateMemory,

        [System.String]
        $requests,

        [System.String]
        $time,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        $schedule,

        [ValidateSet('HttpLevel','TcpLevel')]
        [System.String]
        $loadBalancerCapabilities,

        [ValidateSet('true','false')]
        [System.String]
        $orphanWorkerProcess,

        [System.String]
        $orphanActionExe,

        [System.String]
        $orphanActionParams,

        [ValidateSet('true','false')]
        [System.String]
        $rapidFailProtection,

        [System.String]
        $rapidFailProtectionInterval,

        [System.String]
        $rapidFailProtectionMaxCrashes,

        [System.String]
        $autoShutdownExe,

        [System.String]
        $autoShutdownParams,

        [System.String]
        $limit,

        [ValidateSet('NoAcion','KillW3wp','Throttle','ThrottleUnderLoad')]
        [System.String]
        $action,

        [System.String]
        $resetInterval,

        [ValidateSet('true','false')]
        [System.String]
        $smpAffinitized,

        [System.String]
        $smpProcessorAffinityMask,

        [System.String]
        $smpProcessorAffinityMask2,

        [System.String]
        $processorGroup,

        [ValidateSet('MostAvailableMemory','WindowsScheduling')]
        [System.String]
        $numaNodeAssignment,

        [ValidateSet('Soft','Hard')]
        [System.String]
        $numaNodeAffinityMode
    )

    #Remove debug from params
    $psboundparameters.Remove('Debug') | Out-Null
    $psboundparameters.Remove('Verbose') | Out-Null

    #TODO - schedule fix up 
    $psboundparameters.Remove('schedule') | Out-Null

    # Check if WebAdministration module is present for IIS cmdlets
    if(!(Get-Module -ListAvailable -Name WebAdministration))
        {
            Throw 'Please ensure that WebAdministration module is installed.'
        }

    #Enumrate all params and act if necessary 
    foreach($psbp in $PSBoundParameters.GetEnumerator())
    {
          Write-Debug "Checking value for $($psbp.Key)"
          
          # We get current status and compare it with desired state  
          if( (Invoke-AppPoolSetting -propertyName $psbp.Key -action get) -ne $psbp.Value )
          {
                Write-Verbose "Setting value for $( $psbp.Key ) to $( $psbp.Value )"
                Invoke-AppPoolSetting -propertyName $psbp.Key -value $psbp.Value -action set
          }

    }

    # Our dsc does not require reboot
    $global:DSCMachineStatus = 0


}

 

The whole magic is hidden in this pice of the code

          # We get current status and compare it with desired state  
          if( (Invoke-AppPoolSetting -propertyName $psbp.Key -action get) -ne $psbp.Value )
          {
                Write-Verbose "Setting value for $( $psbp.Key ) to $( $psbp.Value )"
                Invoke-AppPoolSetting -propertyName $psbp.Key -value $psbp.Value -action set
          }

In the ‘if’ statement we invoke get of setting (with use of our special function 🙂 ) and compare it with value passed. If there is mismatch we set appropiate value.

 

And that would be it for creating resource 🙂

 

Finally first testing 😀

So after all of that effort we have been through we can go ahead and give it a go 😀 I prepared a small example (which accordingly to best practices is part of the module )

This is what we will be playing with

2015-08-16_23h13_48

 

So lets configure it in our DSC config….

configuration Config_AppPoolDefaaults
{
    param
    (
        # Target nodes to apply the configuration
        [string[]]$NodeName = 'localhost'
    )

    # Import the module that defines custom resources
    Import-DscResource -ModuleName 'cWebAdmin'
    Import-DscResource –ModuleName ’PSDesiredStateConfiguration’

    Node $NodeName
    {
       
        # still pointing to defaults - to be changed 
        cWebAppPoolDefaults ConfigureAppPoolDefaults 
        {
            Name                =  'applicationPoolDefaults'        # by pecyfying 'applicationPoolDefaults' as name we show we want to act on default  
            managedPipelineMode =  'Integrated'            
            autoStart           =  'true'
            startMode           =  'AlwaysRunning'
        }
           
    }
}

 

Once invoked you will see similar output

PS C:\temp> Config_AppPoolDefaaults


    Directory: C:\temp\Config_AppPoolDefaaults


Mode                LastWriteTime         Length Name                          
----                -------------         ------ ----                          
-a----        16-8-2015     23:20           2142 localhost.mof                 

 

Then its the time to make that big cmdlet come to alive 😀 ….

 

Amongst many other reults (which I dont show here for clear of this post ) we see i.e.

2015-08-16_23h28_29

And later when setting is not as we wanted it … we see correction

2015-08-16_23h31_30

 

 

Results after applying in IIS ….

2015-08-16_23h32_24

 

 

 

Summary

I think this is great – power of DSC is really out there and I think we should be reaching out to a great tool like that! If you would have any comments or suggestions as usual please leave a comment or participate in this project on GitHub

In the next post we will be focusing on getting this up to level where you can drop it on production servers.  Stay tuned!

 

 

rafpe

Leave a Reply

Your email address will not be published. Required fields are marked *