Tuesday, 27 March 2012

Using PowerShell to modify configuration files in IIS when the Session Count is 0

(Apologies for re-posting this – I thought I was deleting a draft post and ended up deleting the actual post!)
I was looking at Stack Overflow today and I saw a question where a user wanted to change the Web.config when the number of Sessions reaches 0. He wasn’t aware of any way to find out the Session Count, or any automated way to do this. I thought I’d have a go anyway using a C# .NET Console Application anyway … but then I discovered PowerShell!

To summarise PowerShell, think of it like a command prompt on steroids! Not only can you do the usual process starting, killing and file system navigation, but it provides a much more streamlined command system. You also have hundreds, if not thousands of commands at your disposal.
So let me talk you through how I would have done this in .NET and then the equivalent PowerShell implementation I used.

Goals of the application

  1. Get the Session Count of IIS in total from a performance counter
  2. If Count > 0, quit
  3. Stop IIS
  4. Change web.config
  5. Start IIS

Retrieving performance counters in .NET for the Session Count

I’ve recently passed my TS: Data Applications in the .NET 4 Framework exam, which covered how to create and retrieve performance counter data into your applications. So there’s step one – wait until the Active Session Count is 0. An example of retrieving this is:

var counter = new PerformanceCounter(
  "ASP.NET Applications",    
  "Sessions Active",    
  "__Total__",    
  true);   

var sessionCount = counter.RawValue;   

if( sessionCount > 0 )  
{  
   Console.WriteLine("Session Count is {0} - exiting", sessionCount );  
   return;  
}

These values match up to those in the Performance Monitor (perfmon.exe).

In PowerShell, performance counters are easily retrieved as well. There is a specific command for retrieving them. PowerShell declares variables inline using a $ prefix, so here is how to retrieve the same counter in PowerShell. I saved these to a file called “ChangeIIS.ps1”:



   1: # Set up the string
   2: $perfCounterString = "\asp.net applications(__total__)\sessions active"
   3:  
   4: # Retrieve the counter
   5: $perfCounter = get-counter -counter $perfCounterString 
   6:  
   7: # Retrieve the raw value for the first (and only) counter
   8: $rawValue = $counter.CounterSamples[0].CookedValue 
   9:  
  10: if( $rawValue -gt 0 )
  11: {
  12:     write-host Session Count is $rawValue - exiting
  13:     exit
  14: }

Stopping & Starting IIS

This process is straightforward as well in both languages. First C#:



   1: var iisProcess = ServiceController.GetServices().First(s => s.ServiceName == "IISADMIN");
   2: iisProcess.Stop();
   3: 
   4: // Perform tasks
   5:  
   6: iisProcess.Start();

Now PowerShell:



   1: stop-service "IISADMIN"
   2:  
   3: # Perform tasks
   4:  
   5: start-service "IISADMIN"

Modifying the web.config

Unfortunately, I couldn’t find a way of using the in-built System.Configuration classes to open configuration files that are outside of your application. Instead, I just resorted to XmlDocument:



   1: var location = "d:\\web.config";
   2: var newTimeout = "20";
   3:  
   4: var xmlDoc = new XmlDocument();
   5: xmlDoc.Load(location);
   6: xmlDoc.SelectSingleNode("//sessionState").Attributes["timeout"].Value = newTimeout;
   7: xmlDoc.Save(location);

I know what your thinking. How can we access the file in PowerShell? One of the fantastic things with PowerShell is that you have access to all of the .NET classes (including static methods and classes) at your disposal. So here is the same code in PowerShell:



   1: # Set values 
   2: $location = "d:\web.config"
   3: $newTimeout = "20"
   4:  
   5:  # Open file and change value
   6: $doc = new-object System.Xml.XmlDocument
   7: $doc.Load($location)
   8: $doc.SelectSingleNode("//sessionState").timeout = $newTimeout 
   9: $doc.Save($location)

Notice how I am using a “timeout” variable directly. PowerShell has exposed the attribute as a property of the .SelectSingleNode() method. How do I know that? Well, if you execute this in isolation, you’ll get a nice helpful output on all of the properties available to use directly:



PS C:\> $doc.SelectSingleNode("//sessionState")
 
timeout                                                     #text
-------                                                     -----
20                                                          20

Now we have a PowerShell script in its entirety:



   1: # PowerShell script to modify the 'timeout' value in the specified web.config
   2: # when no Sessions are in use.
   3:  
   4: # Constants used throughout application
   5: $webConfig = "d:\web.config"
   6: $newTimeout = "20"
   7: $sessionCount = 0
   8:  
   9: ## BEGIN
  10: write-host Getting performance counters ...
  11:  
  12: $perfCounterString = "\asp.net applications(__total__)\sessions total" 
  13: $perfCounter = get-counter -counter $perfCounterString 
  14: $rawValue = $perfCounter .CounterSamples[0].CookedValue 
  15:  
  16: write-host Session Count is $rawValue
  17:  
  18: if( $rawValue -gt $sessionCount)
  19: {
  20:     write-host Session Count = $rawValue - exiting
  21:     exit
  22: }
  23:  
  24: write-host Stopping IIS
  25: stop-service "IISAdmin"
  26:  
  27: # Open file and change value
  28: $doc = new-object System.Xml.XmlDocument
  29: $doc.Load($webConfig)
  30: $doc.SelectSingleNode("//sessionState").timeout = $newTimeout 
  31: $doc.Save($webConfig)
  32:  
  33: write-host Starting IIS
  34: start-service "IISAdmin"
  35:  
  36: write-host Done!
  37: ## END

Granting permissions to the Script

We’re not done yet. PowerShell also has a security feature that doesn’t allow users to just run PowerShell scripts as soon as they are created. They have to come from a credible source to be run straight away.
In order to run this script, we need to tell PowerShell to bypass security checks for this specific process (or user). For security reasons, I will grant access for this process, as we will only want to run this script once.


  1. Open a Command Prompt – Right Click – Properties - "Run As Administrator"
  2. Type powershell.exe
  3. Type Set-ExecutionPolicy –scope Process Bypass
  4. Type sl <directory of file>sl acts like cd on the command prompt
  5. Type $ ‘.\ChangeIIS.ps1’
And off we go!

Summary

Where opportunities arise to try out new technologies, it is always work having a go. PowerShell isn’t difficult to learn. In fact, its actually very powerful and intuitive. It is also a great ‘Immediate Window’ style interface to try out .NET code. I’m starting to use it quite a lot for even simple calculations:


224 ==> [System.Math]::Pow(2,24)

And more importantly – I hope I win that bounty question!





Recent Edits:



  • 28/3/12 - Changed performance counter to use "Sessions Active" instead of "Sessions Total" as Sessions Total inclues Abandoned (forcefully ended), Timed Out and Active.