0

PowerShell – using Nlog to create logs

If you are after a logging framework I can recommend you one I have been using not only in Windows but also in C# development for web projects. Its called NLOG and it is a quite powerfull , allowing you to log not only in specific format or layout – but also do the things to have reliable logging ( by having i.e. multiple targets with failover ) with required performance ( i.e. Async writes ). Thats not all! Thanks to out of the box features you can log to flat files , databases , network endpoints , webapis … thats just great!

The Nlog is available in GitHub here so I can recommend that you go there and get your self familiar with the Wiki explaining usage and showing some examples.

At this point of time I can tell you that you can use XML config file either configure logger on the fly before creation. In this post I will show you the both options so you would be able to choose best.

 

The high level process looks following :

  1. Load assembly
  2. Get configuration ( or create it )
  3. Create logger
  4. Start logging

 

Nlog with XML configuration file

The whole PowerShell script along with configuration module looks following :

Now the thing that can be of interest of you .. the waywe load our assembly. What I use here is loading of byte array and then passing that as parameter to assembly load method.

$dllBytes = [System.IO.File]::ReadAllBytes( "C:\NLog.dll")
[System.Reflection.Assembly]::Load($dllBytes)

Reason to do this that way is to avoid situations where we would have the file locked by ‘another process’. I had that in the past and with this approach it will not happen 🙂

 

The next part with customized data – is used when we would like to pass custom fields into our log. The details are described here on Nlog page

 

After that I’m loading configuration and assigning it

$xmlConfig                       = New-Object NLog.Config.XmlLoggingConfiguration("\\pathToConfig\NLog.config")
[NLog.LogManager]::Configuration = $xmlConfig

 

Nlog with configuration declared on the fly

As promised it might be that you would like to use Nlog with configuration done on fly instead of centralized one. In the example below I will show you file target as one of the options. There is much more so yu may want to explore remaining options

    # Create file target
    $target = New-Object NLog.Targets.FileTarget  

    # Define layout
    $target.Layout       = 'timestamp=${longdate} host=${machinename} logger=${logger} loglevel=${level} messaage=${message}'
    $target.FileName     = 'D:\Tools\${date:format=yyyyMMdd}.log'
    $target.KeepFileOpen = $false
    
    # Init config
    $config = new-object NLog.Config.LoggingConfiguration

    # Add target 
    $config.AddTarget('File',$target)

    # Add rule for logging
    $rule1 = New-Object NLog.Config.LoggingRule('*', [NLog.LogLevel]::Info,$target)
    $config.LoggingRules.Add($rule1)

    # Add rule for logging
    $rule2 = New-Object NLog.Config.LoggingRule('*', [NLog.LogLevel]::Off,$target)
    $config.LoggingRules.Add($rule2)

    # Add rule for logging
    $rule3 = New-Object NLog.Config.LoggingRule('*', [NLog.LogLevel]::Error,$target)
    $config.LoggingRules.Add($rule3)

    # Save config
    [NLog.LogManager]::Configuration = $config

    $logger = [NLog.LogManager]::GetLogger('logger.name')

 

Engineers…. Start your logging 🙂

Once done not much left 😀 you can just start logging by typing :

$logger_psmodule.Info('some info message')
$logger_psmodule.Warn(('some warn message')
$logger_psmodule.Error(('some error message')

 

 

1

Docker on Windows – Running Windows Server 2016

So without any further delays we go ahead and create our environment to play around with containers however this time we will do it on windows!

As you know with release of Windows Server 2016 TP3 we have now the ability to play around with containers on Windows host. Since Docker is under heavy development it is possible that a lot will change in RTM therefore check out for any updates to this post 😀

If you are happy automating admin like I’m probably one of the first commands you run would be ….

powershell.exe

… 🙂 of course – the windows engineer best friend !

 

To get this show stared I’m using Windows Server 2016 TP3 on Azure as that gives the biggest flexibility. Microsoft have posted already some good points how to get started using Docker. That documentation (or more less technical guide ) is available here. It explains how to quickly get started.

So we start off by logging into our Windows host and starting off powershell session :

ws2016tp3_ps1

 

Cool thing about which I wasn’t aware is syntax highlighting (something that ppl on unix had for a while 🙂 ) which makes working with PS and its output more readable (in my opinion)

So as mentioned in my previous post you have option to manage containers with Docker (as we now know it on ubuntu i.e. ) or with PowerShell. Since I have been working with with Docker already I decided to investigate that route and leave powershell for a bit later .

Following documentation which I have linked above we can see that Microsoft have been really good and prepared for us script which will take of initial configuration and download of all necessary docker tools.

In order to download it we need to execute the following command :

wget -uri http://aka.ms/setupcontainers -OutFile C:\ContainerSetup.ps1

If you would rather to get to the source of the script its available here

Once downloaded you can just start the script and it will take care of required configuration and download of the images. Yes … downloading of those images can take a while. Its approximately ~18GB of data which have been downloaded. So you may want to start the configuration before your favourite TV show or maybe a game of football in park 😀

Once completed we have access to the goodies – we can start playing with Docker. First thing which is good to do is to check out our Docker information … easily done by

Docker info

In my case the output is following :

ws2016tp3_docker_info

 

Out of my head what is definitely worth of investigating is logging driver ( when used a bit differently allows you to throw Docker logs to centralised system i.e. ElasticSearch … but about that a bit later 😀 ). Rest we will investigate along with this learning series of Docker on Windows.

Now what would a Docker be without images! After running that long time taking configuration process we get access to windows images prepared for us. If you have not yet been playing around with those then you can get them by issuing

docker images

With that we get the available images :

ws2016tp3_docker_images

First thing to notice is that approximate size of default image is ~9,7GB which is a question in this days – is it a lot ? I think you need to answer this question by yourself 🙂 or by waiting for MS to provide a bit more details (unless those our out and I haven’t found them 🙂 ). At the moment with my experience with Docker on Ubuntu – set up of Linux host and containers is a matter of minutes. So that GB of data on Windows might be a bit of show stopper on throwing Windows hosts for Docker.

Now since we have our image it might be useful to get more detailed information. We can get them by issuing command

docker inspect <Container Id> | <Image Id>

The results are following :

[
{
    "Id": "0d53944cb84d022f5535783fedfa72981449462b542cae35709a0ffea896852e",
    "Parent": "",
    "Comment": "",
    "Created": "2015-08-14T15:51:55.051Z",
    "Container": "",
    "ContainerConfig": {
        "Hostname": "",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,
        "ExposedPorts": null,
        "PublishService": "",
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
        "Env": null,
        "Cmd": null,
        "Image": "",
        "Volumes": null,
        "VolumeDriver": "",
        "WorkingDir": "",
        "Entrypoint": null,
        "NetworkDisabled": false,
        "MacAddress": "",
        "OnBuild": null,
        "Labels": null
    },
    "DockerVersion": "1.9.0-dev",
    "Author": "",
    "Config": null,
    "Architecture": "amd64",
    "Os": "windows",
    "Size": 9696754476,
    "VirtualSize": 9696754476,
    "GraphDriver": {
        "Name": "windowsfilter",
        "Data": {
            "dir": "C:\\ProgramData\\docker\\windowsfilter\\0d53944cb84d022f5535783fedfa72981449462b542cae35709a0ffea89
6852e"
        }
    }
}
]

 

So here we go – we will create our first container by running the following command . It will give us regular output and will run in background.

docker run -d --name firstcontainer windowsservercore powershell -command "& {for (;;) { [datetime]::now ; start-sleep -s 2} }

It gives regular output. In order to see what container is outputting you can issue command

docker logs <container Id>|<container name>

 

Thats all fine … but how do we do any customisations to our container ? The process is fairly simple we run a new container and make our changes. Once we are happy with changes we have implemented we can commit the changes and save our image. We will quickly explore this by creating container which would host our IIS web server.

We begin with creating a new container and entering interactive session

docker run -it --name iisbase windowsservercore powershell

Once your container is up we are directly taken to powershell session within that container. We will use the well known way to get base image configured. What we are after here is adding web server role using PS. First lets check if its definitely not installed :

Get-WindowsFeature -Name *web*

ws2016tp3_docker_imagecust01

After that we will just add the web server role and then exit container. Lets issue the command for installation of role :

PS C:\> Add-WindowsFeature -Name Web-Server

ws2016tp3_roleinside_docker

 

Before we exit there is something which is worth of mentioning … speed of containers (at least at the moment of writing this blog where ppl at MS are still working on it 🙂 ). It can be significantly improved by removing anti malware service from you base image . This can be done by running the following command :

Uninstall-WindowsFeature -Name Windows-Server-Antimalware

 

Now we can exit our container by simply typing

exit

Small thing worth of mentioning 🙂 clipboard string content paste into containers have been limited to ~50 characters which is a work in progress and should be lifted up in next releases.

 

Ufff so we got to the point where our container have been configured it. Its time to build image from it. This can be done (at the moment ) only on containers which are stopped. To execute commit run :

# docker commit <container Id> | <contaner Name> <RepoName> 
docker commit 5e5f0d34988a rafpe:iis 

 

The process takes a bit of time however once completed we have access to our new image which allows us to spin multiple containers 🙂 If you would like to evaluate the image created you can use the approach and commands discussed earlier in this post.

ws2016tp3_docker_commitedimage

 

And as you can see our new image rafpe is slightly bigger than the base image (this is due changes we made).

Let’s go ahead and do exactly what we waited for – spin a new container base on this image

docker run -d --name webos -p 80:80 rafpe:iis

Now at the moment of writing I could not get connected on exposed port 80 from container-host by issuing something in lines of

curl 127.0.0.1:80

According to information which I have found on MSDN forums people are experiencing the same behaviour . Which means (if enabled on firewall ) you can get from external systems to your container exposed port (check if you have NAT correctly set up ).

 

Now to add something useful if you would like to try different approach for this exercise with Docker. To find different images use the following command :

docker search iis

Uff – I think that information should get you going and please be advised that this is a learning course for me as well 🙂 so if I made some terrible misleading in information here please let me know and  I will update that.

To not leave without pointing you to some good places here are links used by me for this :

 

Hope you liked the article! Stay tuned for more!

 

 

0

MacOs – Multiple terminals with customised color scheme

If you are like me 😀 So not closing yourself only to one operating system you then probably operate between the world of Windows and world of Linux 😀

At the moment I have set up my working environment in a way that allows me to work wit both systems. So one one end I got the new and shiny Windows 10 and on the other boot I got Mac OS.

And on Mac Os I have been looking for software that would give me better control and visibility of my sessions than the standard terminals app. With a bit of looking around I found couple of alternatives and the one that really got my attention is iTerm

The way it looks its more than satisfying 🙂 You can see detailed view of horizontal split on the screen below :

Screeny Shot 29 Aug 2015 13.15.42

 

The program has a lot of cool color schemes to offer. What I also did was to edit my profile file accordingly to the mentioned solutions in this post

If you rather just to get details of modification here it is :

export CLICOLOR=1
export LSCOLORS=GxFxCxDxBxegedabagaced
export PS1='\[\033[01;32m\]\[email protected]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

 

Hope this will help you to customise your environment up to your needs 😀 If you are using other helpful tools feel free to share in comments!

 

0

Rise of Docker – the game changer technology

If you have not yet been working with Docker containers – worse… if you have not yet really hard about Docker and significant changes it brings – then you should find more information!

In simple words Docker does the thing we always wanted – it isolates applications from our hosts layer. This enables possibility of creating micro services that can be dynamically scaled / updated / re-deployed !

If you would like to imagine how this whole Docker works then I’m sure that by looking on image below you will get the grasp of the idea behind!

DockerWithWindowsSrvAndLinux

 

 

So things to keep in mind are that Docker is not some black magic box … it requires underlying host components to run on top of it. What I mean by that If you need to run Windows containers you will need Windows host and the same principal will apply for the Linux container. You will also need Linux Host.

Up to this point there was no real support on Docker containers on Windows. However by the time of writing this document Microsoft has released Windows Server 2016 which brings major breaking changes and primarly of our interest the support to containers!

One of the thing that Microsoft have made people aware is that you will be able to  manage containers with Docker and with Powershell … but …. yep there is a but – containers created with one cannot be managed with other one . I think thats a fair trade of but thats something that potentially is going to be changed.

 

In the meantime I invite you to explore docker hub and help yourself by getting more detailed information when exploring the docker docs 

In one of the next posts we will discuss how to get Windows docker container running on Windows Server 2016 (TP3 ) !  With that keep intro to Docker I hope to see you again!

 

 

0

Manage your GitHub gists with GistboxApp

If you are like me 😀 and understand that sharing with your knowledge is something great – then this application might be for you. I dont have yet so many Gists on GitHub but getting them correctly managed is something I wanted to have.

Thats how I came across GistboxApp which I personally think its a great value added for anyone having to manage gists. It gives you option of creating labels , adding files , editing … and much more. I think if you use GitHub you might an to give it a spin.

2015-08-28_15h13_57

 

 

It also has Chrome cool addon which enables you to quickly create gits on the fly!

 

Looking out to hear from you what else are you using 😀

0

PowerShell – using PSDefaultParameterValues to make your life easier

Just a quick post to let you know that you can make your life easier (most of the times 😀 ) when for example using default paramaters for your PowerShell cmdlets. This is done by using $PSDefaultParameterValues which allows you to specify default parameters for parameters in your cmdlets. It will be available in session in which you are working in.

Example ?

 

$PSDefaultParameterValues.Add('Invoke-WebRequest:Proxy','http://comeproxy.server:80') 
$PSDefaultParameterValues.Add('Invoke-WebRequest:ProxyUseDefaultCredentials',$true)

 

Just remember that those settings will alive only in your current powershell session.

 

 

2

Powershell – Connect to TFS wiith alternative credentials

In this short post I will just show you how can you connect to your TFS server (from PowerShell) using alternative credentials. As it might be useful when you automate your infra to peform certain task. This script assumes you do have required TFS dlls available on your machine. If not you need to get them and load them on runtime

This is how I load it in a seperate parts of my scripts

        # load the required dll
        [void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Client')

 

Here is the script itself …

0

Splunk – Network components diagram

If you have been using Splunk (or need to make a design for that system ) it is handy to visualize how Splunk works and what ports you are looking for.

While browsing Splunk forum base I have came across this great diagram ( link to source *thx for finding that Matthijs  )

spluk

 

 

I think this is really helpfull to see exactly how does components work with each other.

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.