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!

 

 

0

PowerShell – Automate multilanguage maintenance page with culture objects

maintenance …. doesnt that word just sounds like fun in the world of IT people 🙂 I think it all depends on you and how you set up your approach to the situation.

I recently have been overseeing maintenance where we had a single page for one of my customers. And that would be just fine and without a problem if not the fact within that single web page he had HTML containers for multiple languages and multiple countries.

Ok – but still ? Where is the problem with that ?  For purposes of ilustration I will try to come up with some text to give you an idea of how this would look like :

# -------- other code removed for visibility 

<p> On Monday August 17 2015 between 00:01 and 00:02 we will be undergoing maintenance.

Banana!
</p>

# --------- some other HTML here 

<p> El lunes 17 de agosto 2015 entre las 00:01 y las 00:02 estaremos experimentando mantenimiento.

¡Plátano! </p>

# --------- and again a lot of code here and there 

<p>Le lundi 17 Août 2015 0 heures 01-00h02 nous serons en cours de maintenance.

Banane! </p>


# --------- and again a lot of code here and there ... and so on and so on :O

 

Yey :O So I asked – ok how do you generate that file … unfortunately … this was a manual work which means someone would just go and modify those dates manually also using some google translate to get appropiate days/ months names.

 

We must automate this!

Yes! Cannot agree more that this cannot be like that. I’m fine with everlasting static code (altough I think the whole should be dynamically generated ) however lets start small 🙂

So what can do here …. we must identify moving parts. In our case the moving parts in first instance is the Country. Then we can have multiple different locale for country. Example ? Belgium … We can have English,French and German. Lastly we identify the property of locale like day of week/month etc

Now our code in source could look like

# -------- other code removed for visibility 

<p> On {US_en-US_DayOfWeek} {US_en-US_Month} {US_en-US_Day} {US_en-US_Year} between {US_en-US_StartDate} and {US_en-US_StopDate} we will be undergoing maintenance.

Banana!
</p>

# --------- some other HTML here 

<p> El {ES_es-ES_DayOfWeek} {ES_es-ES_Day} de {ES_es-ES_Month} {ES_es-ES_Year} entre las {ES_es-ES_StartDate} y las {ES_es-ES_StopDate} estaremos experimentando mantenimiento.

¡Plátano! </p>

# --------- and again a lot of code here and there 

<p>Le {FR_fr-FR_DayOfWeek} {FR_fr-FR_Day} {FR_fr-FR_Month} {FR_fr-FR_Year} {FR_fr-FR_StartDate}-{FR_fr-FR_StopDate} nous serons en cours de maintenance.

Banane! </p>


# --------- and again a lot of code here and there ... and so on and so on :O

 

So what have we done here ? Well we have introduced variables that will allow us to modify moving parts. If you look at single one {ES_es-ES_DayOfWeek}

We have it in ‘{}’ which will allow for quick search within content of files. Then we have it in capitols as country followed by locale and lastly by property name.

All of those divided by using ”_’ . Easy isnt?

 

Let the coding begins!

Since I want to avoid situation where I would have 50 ‘if statements’ or ‘replace’ statements in my code I will code with thinking of

  • modularity of the code
  • ease of extending this

 

Great! So now we have already prepared file contents with our customized variables and now we need to figure a way of putting that into the game 😀

 

So lets see what happened here

Here I have created my sel hashArray to be used within the script. As you can see the country is primary unique key for us. Each country can have multiple locales and maybe in future extra other settings.

# we define our countries
$pageCountries= @(    @{country="NL";[email protected]("nl-NL","en-US")}, `
                      @{country="BE";[email protected]("nl-BE","fr-BE","de-DE","en-US")},`
                      @{country="FR";[email protected]("fr-FR")},`
                      @{country="DE";[email protected]("de-DE")},`
                      @{country="UK";[email protected]("en-GB")},`
                      @{country="US";[email protected]("en-US")} 
)

 

Next I defined time slots for this maintanance. On purpose I used [datetime] object as I like the fact of just passing variables and not trying to parse from string 🙂 At the moment of writing duration is applied for all countries but as you can see it could be that we customize if for each country

# maintanance start date 
$maintananceStartDate=[datetime]::new(2015,8,16,1,0,0) # year,month,day,hour,minute,second

# maintanance duration (should be per country maybe ? )
[int]$maintananceDuration = 4

# stop time is 
$maintananceStopDate = $maintananceStartDate.AddHours($maintananceDuration)

 

Next we do iterations. We start off with countries and then for each of the countries we get into country locale :

# We start with each country
foreach($singleCountry in $pageCountries)
{
   
   # we then go for each locale
   foreach($languageLocale in $singleCountry.locale)
   {

 

From here we now will be creatign customzied variables for replacements. We start off by getting the locale culture for our current iteration

# get culture 
        $cultureCurrent = New-Object system.globalization.cultureinfo($languageLocale)

 

Having that we go ahead and create our properties and assign their values accordingly. If you notice later by just adding properties we will be auto-extending possible scope of variables in file ….

# We define our props 
        $props = @{ dayOfWeek           = $cultureCurrent.DateTimeFormat.DayNames[ [int]$maintananceStartDate.DayOfWeek ]; 
                    day                 = $maintananceStartDate.Day; 
                    month               = $cultureCurrent.DateTimeFormat.MonthNames[ $maintananceStartDate.Month ];
                    startTime           = $maintananceStartDate.ToShortTimeString();
                    stopTime            = $maintananceStopDate.ToShortTimeString();
                    datetime            = $maintananceStartDate.ToShortDateString()}

What is interesting here is that dayOfWeek comes from array of enums for specific language selected by integer value of our current day for maintanance.

DayNames looks as following in PS

DayNames                         : {Sunday, Monday, Tuesday, Wednesday...}

 

Cool – so lastly we go into replacing mode 🙂 As mentioned just a moment ago – with just adding single property into that array we get it auto added. This is done by iterating every named property in there

 # We need to now interate each of the props and make appropiate replacements
        foreach($item in $props.GetEnumerator()|select Name -ExpandProperty Name) 
        {

 

And then there is not much left except of just replacing those values

            $filter = "{" + [string]::Format('{0}_{1}_{2}',$singleCountry.country, $languageLocale, $item) + "}"

            Write-Host "Our filter is $filter" -ForegroundColor Yellow
            Write-Host "Target Value is $($props[ $item ] )" -ForegroundColor Yellow

            $maintanancePage = $maintanancePage.Replace( $filter, $props[ $item ] )

 

And thats IT ! Automated , streamlined , prone to user error 😀

Future todo

At the moment I think it is a good moment to have basic version working before we start rumbling with changes 😀 I defnitely will add Pester tests to it and make sure it can be more customized. Im thinking of per country advanced settigns maybe … ? We will see – will keep you updated.

 

 

 

0

Powershell – build complex module with functions dependency tree

logo-powershell-520x245Hey! So the time has come to share with you one of my recent achievements within automation. As you may have noticed in subejct of the post we will be focusing on complex functions with dependencies on onther functions within module. If that would not be enough … we will execute them remotely within session. Isnt that just uber cool ?

So in my last post I focuses on brief intro to what exporting of functions could look like. Today we start off with definition how we set up our functiosn and what we will use to build dependencies ( of course keeping it all under control )

How do you build complex functions ?

This might sound trivial however it is quite important to get this idea before you go ahead and build a module with 30 cmdlets and 3000 lines of code. From my DevOps experience I can tell you that wherever possible I start building from generic functions which then I use in more specific functions (a form of wrappers level above )

I think if we were to think of visualisation we could say we would get something in lines of :

2015-08-11_11h56_49

 

Someone just looking at it could say it looks really promising and professional 😀 Well it does. It is all about low level functions (the generic ones) to work more less with RAW data objects without performing complex validations. This validations and remaining high level functionality would be done by high level functions. 

 

Sample code which I could use for executing such dependent functions could look as follow :

                # Required functions on the remote host
                $exportNewObjGeneric  = Export-FunctionRemote New-ObjGeneric                                                                                                                                             
                $exportAddProps       = Export-FunctionRemote Add-ObjProperties 
                $exportGetAdSite      = Export-FunctionRemote Get-MyAdSite

                $remoteFunctions = @($exportGetAdSite, $exportNewObjGeneric, $exportAddProps)

Invoke-Command -Session $Session -ScriptBlock {   
            
                    # we recreate each of required functions
                    foreach($singleFunction in $using:remoteFunctions)
                    {
                        . ([ScriptBlock]::Create($singleFunction));
                    }
                 # ---- some other code doing magic -----
}

 

 

Great! Since it sounds so easy to implement … where is the catch …. 😀 Of course there is one … look what potentially could happen if you just go ahead and start referencing functions within a module ….

2015-08-11_15h59_13

 

It will not take a long time when you will lose track of what references what and where are your dependencies. This is just asking for trouble as you will practically not be able to assert what will be consequences of yoru changes on one end. I can guarantee you would get a butterfly effect in this scenario!

You will quickly lose ability to properly manage the code and automation will become more than a nightmare than a pleasure!

 

I have seen that to – however a bit differently. I though we could utilize something that every function of mine has – code based help!

Look at the example of function below – for purposes of this post I have ammended the code based help

2015-08-11_13h22_06

 

 

Have you noticed anything special ? …. Well if you didnt let me explain ….

 

Reference functions which are dependencies

Yes! This is the road to ultimate automation. I have came up with idea which can be describe as following :

  • Functions can nest (have a dependency  )  only on 1 level – so not dependency in dependency in dependency (but maybe you will come up with some more elegant way to overcome this 😀 )
  • Generic functions should not have dependencies on custom functions

With those 2 points I was able to continue with this solution. Therefore I have ammended a bit of the code we used last time and came up with the following :

 

Now this functions references one of which one already discussed called ‘Export-FunctionRemote’ (available @ Github ).

So what we got from the above ? Well we got something really great. In a controlled way by decorating our function with commented based help and specyfing  RequiredFunction<Some-FunctionName> it will be cosnidered as dependency in out script.

    <#
        .SYNOPSIS
        Do xyz 

        .FUNCTIONALITY
        RequiredFunction<Get-NobelPrice>
    #>

 

Finally use example

So we need to use what we have just received. I wont be taking long to explain – this is the pure awesomness of automation 🙂 …

                # Aqquire function name
                $functionName = $MyInvocation.MyCommand
             
                # get remote functions into remote script block
                Write-Verbose "Exporting functions for $functionName"
                $remoteFunctions = Get-RemoteRequiredFunctions -functionName                    $functionName

               

                Invoke-Command -Session $Session -ScriptBlock {   
            
                    # we recreate each of required functions
                    foreach($singleFunction in $using:remoteFunctions)
                    {
                        . ([ScriptBlock]::Create($singleFunction));
                    }

                    # ---- do magic -----
                }

 

 

Summary

I hope you like the idea of automating yoru functions in generic way to work with Raw data and then use high level functions to really utilize their potential. Given that you have also now received way to perform advanced operations by creating function dependencies.

Of course this is more than extendible – you can buld dependency trees – do more complex unit testign with Pester … there is not limtis 😀

Got feedback / issue ? As usual leave a comment or jump to GitHub / Gists

 

0

Powershell – DSC checklist

As you remember we are in the middle of the series for DSC module creation. I maybe should have mentioned this before but better now than never.  What I’m talking about … well its all about good practice within DSC. Some of them we do apply and some we will apply in our DSC series.

As it is good to have it around I decided to do a repost . Source from http://blogs.msdn.com/b/powershell/archive/2014/11/18/powershell-dsc-resource-design-and-testing-checklist.aspx

1       Resource module contains .psd1 file and schema.mof for every resource

2       Resource and schema are correct and have been verified using DscResourceDesigner cmdlets

3       Resource loads without errors

4       Resource is idempotent in the positive case

5       User modification scenario was tested

6       Get-TargetResource functionality was verified using Get-DscConfiguration

7       Resource was verified by calling Get/Set/Test-TargetResource functions directly

8       Resource was verified End to End using Start-DscConfiguration

9       Resource behaves correctly on all DSC supported platforms (or returns a specific error otherwise)

10     Resource functionality was verified on Windows Client (if applicable)

11     Get-DSCResource lists the resource

12     Resource module contains examples

13     Error messages are easy to understand and help users solve problems

14     Log messages are easy to understand and informative (including –verbose, –debug and ETW logs)

15     Resource implementation does not contain hardcoded paths

16     Resource implementation does not contain user information

17     Resource was tested with valid/invalid credentials

18     Resource is not using cmdlets requiring interactive input

19     Resource functionality was thoroughly tested

20     Best practice: Resource module contains Tests folder with ResourceDesignerTests.ps1 script

21     Best practice: Resource folder contains resource designer script for generating schema

22     Best practice: Resource supports -whatif

5

PowerShell – Local functions in remote sessions with ScriptBlock

logo-powershell-520x245 I think all of have been in situation when we wanted to run local PowerShell functions in remote sessions. If someone is new to the subject you would think there is no challenge here 😀

But wait …. there is 😀 I have personally seen couple of implementations of such ‘workarounds’ and in my opinion the most important is to choose the one that suits you.

Option 1 : Pass the function in script block :

You can directly invoke something in lines of :

Invoke-Command -ScriptBlock ${function:foo} -argumentlist "Bye!"

But when you do a lot of automations like I do this doesnt do any good for me :O

 

Option 2 : Use ScriptBlock

Script block you could say will be our ‘precious‘ 🙂 It seems a lot of ppl underestimate its potential. We should maybe first start of with how we define ScriptBlock.

I think easiest  to say would be something in lines of ‘… its a container for statements or/and expressions which can accept parameters and return value’ 

If you would like to know more please take a look at technet page.

Now you would ask but how this helps us in remote creation of PowerShell functions ? Ohhh it really does! It opens you doors to total automation ….

Look at this simple example (the snippet below comes from StackOverFlow )

$fooDef = "function foo { ${function:foo} }"

Invoke-Command -ComputerName someserver.example.com -ScriptBlock {
    Param( $fooDef )

    . ([ScriptBlock]::Create($using:fooDef))

    Write-Host "You can call the function as often as you like:"
    foo "Bye"
    foo "Adieu!"
}

 

So what happened here ? As we said a moment ago scriptblock is an container for expressions/statements and can accept input and return value(s)….  So we have defined our container of statements as

$fooDef = "function foo { ${function:foo} }"

Its perfectly valid to do so 😀

Next one is execution of our script in remote session and what we do here is we recreate our scriptblock based on definiton

. ([ScriptBlock]::Create($using:fooDef))

And voilla – from that moment you can use your function in remote session as it would be local!

 

One step further – automate exporting of functions

I’m a bit excited writing about this however using the above we will be able to create amazing automations. We will focus on creating multilayer (dependent each on) functions that we will automatically recreate when necessary.

But before we get there we need to automate exporting of our functions. For this we will use the following PS script :

 

 

0

PowerShell – Creating DSC module – Part 2

Hey ,

Without any further delays we will continue our excercise to write yp DSC module containing multiple resources. As this is multi-part post you might want to jump back to first post to read up on preparations and considerations.

 

Objectives for this post:

In this particular post we will focus on creating DSC resource properties and modifying out root module manifest.  Source code in ps1 format for what we will be doing today is available as usual 🙂 on GitHub  ( https://github.com/RafPe/cWebAdmin/blob/master/src/createDscResources.ps1 ) so if you would like to help or have feedback or suggestions leave a comment or just fork the script and request a pull 😀

If you are new to this series of post I’m aiming to get fully operational webapp Pool and website admin DSC module (at the moment of writing this post it will support only IIS 8.5 ). If you would say “Hey! But there is already one from MS available even on GitHub! So why reinvent the wheel ?” then my answer would be ‘The ones available do not perform scope of configurations I want to do – therefore I’m creating one to fullfuill my technical needs and on the other hand I get a nice tech excercise! ‘

 

Investigate your target resource properties:

Let’s go ahead and see what’s available for us. As usual there are multipe ways to achieve your goal 😀 its all about selecting the optimal one. As in majority cases you could go ahead and investigate GUI to see which ones are available for you. Something in lines of :

2015-08-09_18h10_51

 

Hmmmm … but really – how much time will it take you to complete list of all available properties ? So lets go ahead and do it a bit differently.

$props = Get-Item IIS:\AppPools\<YourAppPoolNam> | Get-Member -MemberType NoteProperty

That gives you more less output similar to the following one :

2015-08-09_18h15_19

 

So is that all ? 🙂 Of course it isnt – we are missing quite a lot. The remaining properties are hidden in child elements collections. We get to them by calling :

(Get-Item IIS:\AppPools\<YourAppPoolName>).ChildElements

And that gives you the following output :

2015-08-09_18h17_43

 

And you access the properties of each collection by calling

(Get-Item IIS:\AppPools\<YourAppPoolName>).ChildElements['processmodel'].Attributes | select Name,TypeName

and our output will look similar to the following one :

2015-08-09_18h22_12

 

 

Fine 🙂 we have our resources and more less we know their types (keep in mind that some of int types here – within GUI are enums )

 

Get me the resource!

Ok – so you are hungry for coding 😀 I totally udnerstand – So let’s discuss cmdlet that will be playing main role here for us : which is New-xDscResourceProperty.

This cmdlet has couple of properties which we need to discuss. Lets take a look on example of usage :

$managedRuntimeVersion          = New-xDscResourceProperty –Name managedRuntimeVersion –Type string –Attribute Write -ValidateSet "v4.0","v2.0",""

As you can see we specify couple of important switches :

  1. Name : I think that this will be self explanatory 🙂 What you need to keep in mind that single DSC resource CANNOT HAVE 2 properties with the same name (but you can imagine why 🙂 )
  2. Type This defines what type is the property we will be working with
  3. Attribute :
    [ Key ]     : 
    on a property signals that the property uniquely identifies the resource
    [ Write ] : means we can assign value in our configuration
    [ Read ]  : indicates that property cannot be changed , neither we can assign value to it
  4. Description : Allows you to describe property
  5. Values and ValuesMap : restricts possible property values to specified in ValuesMap

 

It’s coding time 😀

 

Since now you know how to go about this – lets go ahead and create our resources properties and finally our resource. The decision how to tackle all properties and creating each one I leave up to you. At the moment of writing this post in series my script have had defined all of them (however this could have changed 🙂 )

Since I want to create multiple resource properties I define them as following :

   $Ensure                         = 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

# -------------- Rest is removed for clear of this example 
# -------------- script is available @ https://github.com/RafPe/cWebAdmin/blob/master/src/createDscResources.ps1

 

Next to ease assigning them into a new resource I create an array

$xDscProperties [email protected](
       $name,
       # -------------------------- Removed for visibility
       $numaNodeAffinityMode      
    )

 

And once all of that is done we can go ahead and finally create our new resource! In my case this is resource to manage WebApp pool default settings.

# Create resource that will be defining defaults for application pool 
New-xDscResource -Name RafPe_cWebAppPoolDefaults`
                 -FriendlyName cWebAppPoolDefaults`
                 -ModuleName cWebAdmin`
                 -Path 'C:\Program Files\WindowsPowerShell\Modules' `
                 -Property $xDscProperties  -Verbose

As you can see here I’m prefixing the resource name with RafPe_ and I’m doing this because a lot of other people could come up with same name. Altough FriendlyName I’m choosing to be cWebAppPoolDefaults as thats the way I want resource to show up. Next we define our root module name which in my case is cWebAdminFollowed by Path which in this case points to default modules folder. And of course the most important – I specify our resource properties which we have just defined a moment ago.

Once you are happy with your module just hit enter! And after couple of seconds your new module should be ready and good to go!

You should now have your module already available for import .

2015-08-09_19h02_15

 

Modify root DSC module manifest:

Whats now left – we need to modify module manifest. Easiest (and a lot of ppl do it ) is to copy one of already existsing ones and just changed it a bit. What is extremly important is to generate own new GUID 

Module manifest for root module we are working with looks following :

#
# Module manifest for module 'cWebAdmin'
# Author RafPe

@{

# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
GUID = 'f3e1b30a-9292-4ca3-a1f1-572bd64cf460'

# Author of this module
Author = 'RafPe'

# Company or vendor of this module
CompanyName = 'RafPe'

# Copyright statement for this module
Copyright = '(c) 2015 RafPe. All rights reserved.'

# Description of the functionality provided by this module
Description = 'Module with DSC Resources for Web Administration'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '4.0'

# Minimum version of the common language runtime (CLR) required by this module
CLRVersion = '4.0'

# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{

    PSData = @{

    # A URL to the license for this module.
    LicenseUri = 'https://rafpe.ninja'

    # A URL to the main website for this project.
    ProjectUri = 'https://rafpe.ninja'

    } # End of PSData hashtable

} # End of PrivateData hashtable

# Functions to export from this module
FunctionsToExport = '*'

# Cmdlets to export from this module
CmdletsToExport = '*'
}

 

And how do you generate a Guid ? simple … use .Net methods to quickly do that :

[guid]::NewGuid().Guid

 

Summary:

Well thats it for today 🙂 We have narrowed our resources and evaluated their types. We discussed how to create resource property and what parameters we can use fot that instance . And lastly we initially created our module and modified its manifest. So I think we are on really good track to start write of our resource methods in next post in this series.

 

Stay tuned for more! Catch up with all changes/Comment / Pull ->  on GitHub (https://github.com/RafPe/cWebAdmin) and till then !

 

 

 

 

2

PowerShell – Creating DSC module – Part 1

logo-powershell-520x245

Hey ,

As you probably know in world of DevOps a neat feature as DSC is must have and must do considering what it is capable of.  Since recently I have been working a lot with webservers I decided it took to long to get appropiate configuration in place. Considering event though we had some legacy scripts doing work , sometimes we had discripiencis which were really hard to solve (as finding root cause happening randomly on servers was fun )

So with this post I will be introducing you to creation of new DSC module with best practices and experience of myself and people from all over world (thanks GitHub). In many cases I will be referring you to external sources of information but the reason to that is simple – I dont want to duplicate information contained there.

Repo used for this excersise is available under https://github.com/RafPe/cWebAdmin so feel free to comment/contribute as it might be that you will find a better approach.

To be clear from beggining I will be using PSv5 as it offers me the most up to date access to cmdlets

PS C:\Program Files\WindowsPowerShell\Modules> $PSVersionTable

Name                           Value                                                       
----                           -----                                                       
PSVersion                      5.0.10105.0                                                 
WSManStackVersion              3.0                                                         
SerializationVersion           1.1.0.1                                                     
CLRVersion                     4.0.30319.42000                                             
BuildVersion                   10.0.10105.0                                                
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                     
PSRemotingProtocolVersion      2.3                                                         

 

For creation of resource we will be using DSC module called xDscResourceDesigner. If you will check that module you will see the latest updates and description of cmdlets

If you download the tool make sure its available under $env:ProgramFiles\WindowsPowerShell\Modules

Once confirmed you can just import the module by typing :

Import-Module xDSCResourceDesigner

Plan your resource:

Before we go any further it is important to plan your DSC resource accordingly. Think here of “LEGO” 🙂 Ahhhh yes – the good days when you used your imagination to build skyscrapers and pirate ships. In both cases they are much a like 😀 a lot of reusable pieces that interconnects and creates amazing things.

Firstly we will prepare our folder structure. As you can see in example below (taken from Microsoft GitHub) we have :

xNetworking
   xNetworking.psd1
   DSCResources
       MSFT\_xDNSServerAddress
           MSFT\_xDNSServerAddress.psm1
           MSFT\_xDNSServerAddress.schema.mof
       MSFT\_xIPAddress
           MSFT\_xIPAddress.psm1
           MSFT\_xIPAddress.schema.mof
       MSFT\_xIPAddress
           MSFT\_xIPAddress.psm1
           MSFT\_xIPAddress.schema.mof
  • root folder with important module manifest.
  • Then folder containing our resources called DSCResources.
  • Every resource has its own folder and the folder name is the full name of resource (in most of cases this is longer than friendly name).
  • Within each of the folders you have script module (.psm1) and a schema (.schema.mof) file

The decision “To bundle or not to bundle” belongs to you. Since I want to control resources from single module I go for bundle.

 

 

Its all about your resources:

So in order to get going with DSC we will need our resources. Using aforementioned xDscResourceDesigner we will be able to properly create them. In the next post I will go into details of creating resources – their parameters and how to update them if necessary.

 

Whats in DSC :

If we open DSC module  we will find 3 primary DSC TargetResource functions (every DSC resource will have them ):

  • Get – this will get whatever we need to validate
  • Set – this will set whatever we need to set
  • Test – this will validate whatever it is we need to validate and will return a true or false state

The diagram below shows more in detail the flow :

DSC flow

Test-TargetResource is essential as this function is responsible for checking if your target resource exists or not. If it exists, this function must return True otherwise False . Proper writing this function will allow for DSC to deliver desired results.

Set-TargetResource function makes required changes on target resource. It gets called only if  target resource is not in desired state .

The Get-TargetResource function, is used to retrieve the current state of the resource instance.

 

 

Links :

There are several links worth of looking at (that I can recommend you to ) :

There are probably more (and newer ones ) so drop comment and I will update the list so ppl would be able to find them.

 

Summary for part 1:

So this is it for today 🙂 In the next post we will dive in details into :

  • Creating DSC resources and parameters for New-xDscResourceProperty
  • Creating module manifest
  • Talk about reusable scripting and what to avoid when writing scripts
  • …. and much more 🙂

 

Thanks!

RafPe

 

 

 

1

IIS advanced logging – first approach

Hello,

We start of with quite popular subject which is IIS advanced logging. This extra package provided by Microsoft enables for example to have logging of date time with miliseconds or have CPU utilization.

Research and preparations:

So I came across the challenge of quickly coming up with a way of installing it and configuring the servers so we would get the required data. Therefore before proceeding I have made research and determined the following as requirements to complete this challenge :

  1. Obtain MSI to install IIS advanced logging
  2. Determine log file locations
  3. Specify log fields used
  4. Mitigate problem with username in advanced IIS logging module
  5. Specify any extra fields used
  6. Specify fields order in log
  7. Specify any filters

 

1 –  This is fairly simple as downloading from Microsoft Download

2 – This will be the folder where you want to keep your logs. I have not tried network locations – so folders were always local.

3 – From available fields you can specify which ones you are interested. I have choosen the one that brings the most value in my environment

4 – There is a known problem with the username in IIS advanced logging where username is blank. Now I of course have come across this and used the following PowerShell command to mitigate this

# Reconfigure username property
Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST'  -filter "system.webServer/advancedLogging/server/fields/field[@id='UserName']" -name "sourceType" -value "BuiltIn"

5 – It might be that you need to log more than standard fields available out of the box in the solution. That is fairly easy and requires the following code to be executed ( in this instance I’m adding X-Forwarded-Proto header to fields available )

Add custom header X-Forwarded-Proto to available logging fields
Add-WebConfiguration "system.webServer/advancedLogging/server/fields" -value @{id="X-Forwarded-Proto";sourceName="X-Forwarded-Proto";sourceType="RequestHeader";logHeaderName="X-Forwarded-Proto";category="Default";loggingDataType="TypeLPCSTR"} 

6 – Easiest thing to do is to use array which will be executed in order of items. I have defined mine as

# Array of fields we want to be logging 
$RequIisAdvLogFields = "Date-UTC","Time-UTC","Client-IP","Host","Referer","User Agent","Bytes Received","Method","URI-Querystring","URI-Stem","UserName","Protocol Version","BeginRequest-UTC","EndRequest-UTC","Time Taken","Bytes Sent","Status","Substatus","Server-IP","Server Port","Win32Status","X-Forwarded-For","X-Forwarded-Proto","CPU-Utilization"

7 – This is work in progress and will come back to this part.

 

Execution:

Since I have defined my objectives and now I know what I’m working towards to it is easy to create the script. As I would not like to double the content I will just outline steps here and complete script you will fidn tha the bottom of the page (hosted in GitHub) .

  1. I define folder log location and frequency of log rotation
  2. We check if folder exists – if it does not we create it
  3. We need to assign custom permissions in order to be able to save to that folder ( the IIS_IUSRS needs to have write access )
  4. Reconfigure username property
  5. Define required fields in log
  6. Get server available fields
  7. Next for each website we will add log definition (this is something you may want to change )
  8. We delay commits of changes – this is quite important as if you dont do that you will be executing script a really long time
    Start-WebCommitDelay
    
    # Do some work and change configs 
    
    Stop-WebCommitDelay -Commit $true

     

  9. For each of available fields we add it to our defined log definition
  10. Then as last steps I disable standard logging and IIS advanced logging on server level. As last I enable the advanced logging.

 

The whole script is available on gitHub so you are much more than welcome to contribute.  There are points which require work and that is defenitely better error control and adding filtering. However I will be actively developing this to have fully operational tool for IIS advanced configuration.

 

 

1

Road to challenges in IT

Hey ,

It has been long and quiet in last 2 years I think but this times comes to an end. A lot have been happening in regards to learning curve of SCCM/SCOM/ PowerShell  (especially DSc part) and REST Apis.

Nowadays we cannot forget about importance of cloud and hybrid environments and Docker technology!

With all of that I can assure you that from now I will be on regular basis sharing as much as possible from challenges I have came across and from the news I got from the engineering world!

As usual the primary forcus of my experience is providing advanced automation solutions with maintaining security and availability of your services (nop – not forgot -> scalability as well 😀 )

So stay tuned / fork GitHub and enjoy the automation!