Reading IIS.NET Blogs with Powershell

Being a member of the IIS team, I often find myself checking blog posts to see what the members of the product team are blogging about.  However, since Powershell came out, I find myself doing more and more work on my scripts. It's a bit annoying to have to jump out of Powershell to go read blog posts.  As such, I've written a few quick scripts to help me read IIS.NET from my pretty blue shell. For those of you who are already familiar with powershell and don't want to read the long blog post, you can download my blog script from the DownloadCENTER: http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1387

Setting up your Powershell environment


To start, I've written a few supporting functions in my profile.  These functions help me keep my scripts organized and, since I change my scripts quite often, it helps me to sign them as well.

First off, if you haven't created your own certificate for signing code, please go back and take a look at my first Powershell blog post that give you the details on how to do this. 

Next, we need to add a few things to your Powershell profile.  To open your Powershell profile from within Powershell, type:

PS > notepad $profile

First, I add a function to allow us to easily sign our scripts (assuming you have created a cert to sign them wth):

## Sign a file
##-------------------------------------------
  function global:Sign-Script ( [string] $file )
  {
    $cert = @(Get-ChildItem cert:\CurrentUser\My -codesigning)[0]
    Set-AuthenticodeSignature $file $cert
  }
  set-alias -name sign -value sign-script

The next function is used to help me organize things. I have several scripts for various work environments.  I like to organize them by function. So, I keep my IIS scripts in an "IIS" directory, my common scripts in a "common" directory and so on.  Inside each of my script directories, I keep a "load.ps1" script that I can  use to initialize any of my work environments.  Lastly, I create a Powershell drive that matches the work environment name so I can get to my scripts easily. The function below does all the work for me.

## Create a Drive
##-------------------------------------------
  function global:New-Drive([string]$alias)
  {
    $path = (join-path -path $global:profhome -childpath $alias)
    if( !(Test-Path $path ) ) 
    {
      ## Create the drive's directory if it doesn't exist
      new-item -path $global:profhome -name $alias -type directory
    }
    else
    {
      ## Execute the load script for this drive if one exists
      $loadscript = (join-path -path $path -childpath "load.ps1")
      if( Test-Path  $loadscript)
      {
        $load = &$loadscript
      }
    }
    # Create the drive
    new-Psdrive -name $alias -scope global -Psprovider FileSystem -root $path
  }

Within my profile, I simply call this function and pass in an alias. When the function executes it will create a directory with the alias name, if it doesn't exist already. If the directory does exist, it will check for the load.ps1 file inside that path and execute it. Lastly, it will create powershell drive. I have the following calls added to my profile below:

## Custom PS Drives
##-------------------------------------------
  New-Drive -alias "common" 
  New-Drive -alias "iis" 

Go ahead and save your profile now and type these commands:

PS > Set-ExecutionPolicy Unrestricted
PS > &$profile
PS > Sign $profile
PS > Set-ExecutionPolicy AllSigned

The first command sets Powershell into unrestricted mode. This is because we need to execute the profile script and it hasn't been signed yet.  The next command executes the profile. The third command uses the "sign" function that our profile script loaded. Since our profile is now signed, we can set our execution policy back to AllSigned. AllSigned means that Powershell will execute scripts as long as they are signed.

From this point on, we can make changes to our profile and simply call our sign function again before we close our Powershell instance. The next instance of powershell that is opened will have our changes. 

Creating / Using Blog Functionality


Now that we have our environment set up, lets get to the blogging part.  If you've set up your environment right, you can execute the following command:

PS > cd iis:

This command will put you in the iis scripts directory.  Next, create a new blogs script by typing:

PS > notepad blogs.ps1

You'll be prompted if you want to create the file. Go ahead and say yes.  Next, paste the following into the the notepad and save it:

 

## Sets up all custom feeds from feeds.txt
##---------------------------------------------------
function global:Import-Feed
{
  if( $global:RssFeeds -eq $null ) 
  {
    $global:RssFeeds = @{};
  }
  $RssFeeds.Add( "iisblogs",     "http://blogs.iis.net/rawmainfeed.aspx" );
  $RssFeeds.Add( "iisdownloads", "http://www.iis.net/DownloadCENTER/all/rss.aspx" );
}
Import-Feed ## Call Import-Feed so we are ready to go

## Gets a feed or lists available feeds
##---------------------------------------------------
function global:Get-Feed( [string] $name )
{  
  if( $RssFeeds.ContainsKey( $name ) )
  {
    return $RssFeeds[$name];
  }
  else
  {
    Write-Host "The path requested does not exist";
    Write-Output $RssFeeds;
  }
}

## Gets IIS Blogs
##---------------------------------------------------
function global:Get-Blog([int]$index, [int]$last, [int]$first, [int]$open)
{
  $url = (Get-Feed iisblogs)
  return (Get-RSS $url $index $last $first $open)
}

## Gets a specific blog
##---------------------------------------------------
function global:Get-AuthorBlog([string]$creator)
{
  Get-Blog | Where-Object {$_.creator -eq $creator}
}


## Gets Downloads from IIS
##---------------------------------------------------
function global:Get-Download([int]$index, [int]$last, [int]$first, [int]$open)
{
  $url = (Get-Feed iisdownloads)
  return (Get-RSS $url $index $last $first $open)
}

## Gets a generic RSS Feed
##---------------------------------------------------
function global:Get-RSS([string]$url, [int]$index, [int]$last, [int]$first, [int]$open)
{
  $feed = [xml](new-object System.Net.WebClient).DownloadString($url)
  if($index)
  {
    return $feed.rss.channel.item[$index]
  }
  if($open)
  {
    $ieaddr = $env:programfiles + "\internet explorer\iexplore.exe"
    return &(get-item $ieaddr) $feed.rss.channel.item[$open].link
  }
  if($last)
  {
    return ($feed.rss.channel.item | Select -last $last)
  }
  if($first)
  {
    return ($feed.rss.channel.item | Select -first $first)
  }
  return $feed.rss.channel.item
}

Once you've saved this file, close it.  We need to sign this script and execute it by typing:

PS IIS:> sign blogs.ps1
PS IIS:> ./blogs.ps1

Now lets start reading. 

  • Read all Blogs

PS iis:\> Get-Blog

  • Read the last five blog posts

PS iis:\> Get-Blog -last 5

  • Read the first five blog posts

PS iis:\> Get-Blog -first 5

  • Read the 8th blog post

PS iis:\> Get-Blog -index 8

  • Open the 12th blog post and open in Internet Explorer

PS iis:\> Get-Blog -open 12

  • Read all blog posts by Bill Staples

PS iis:\> Get-AuthorBlog bills

  • Read all items in DownloadCENTER

PS iis:\> Get-Download

  • Get titles of all items in DownloadCENTER

PS iis:\> Get-Download | Select Title

Of course, all the laws of Powershell still apply, so I can still do fun stuff like like listing only the blog titles from my blog.

PS iis:\> Get-AuthorBlog TobinTitus | Select Title

I can do the same witht he raw blog output:

PS iis:\> Get-Blog -last 5 | Select pubDate, Creator, Title

Happy reading.

IIS.NET Blogs in Powershell

6 Comments

  • OMG... you really love powershell :) Great stuff.. maybe you should promote it further "eat ps, drink ps, sleep ps!" On the other hand, it would nice to have a column for numbering, for now need to count which row is it before you can issue get-blog -index 9, etc.

  • If you think this is bad, you should see the full script I have on my box. I actually have written a full RSS reader, complete with adding/removing feeds to a list. I tried to add a numbering column but ran into some really kludgy behavior depending on how you query the data. I'm going to take another whack at it sometime next week. I have some more powershell scripts specific to IIS coming this weekend. Stay tuned!

  • Very cool stuff! I just had 2 points about conventions:
    1) You should use "new" instead of "create" as a verb.
    2) We discourage the use of plurals "drive" vs "drives" (english is so irregular that plurals screw up lots of people especially non-english users).


    > On the other hand, it would nice to have a column for numbering, for now need to count which row is it before you can issue get-blog -index 9, etc.
    You can help make this happen in the next release by filing a bug/feature request. You can submit it by using the links at: http://www.microsoft.com/technet/scriptcenter/csc/default.mspx

    Cheers!
    Jeffrey Snover [MSFT]
    Windows PowerShell/MMC Architect
    Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
    Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

  • HOLY COW! Jeffrey Snover graced my blog!

    Thanks for the style pointers. I sort skipped the style guide and started playing with features right away. I'll update my blog and scripts accordingly.

  • Not sure I understand your environment, I get how it works, but I'm having trouble with getting the code to work right. The sign part works, but when I add the "Create a Drive" function, I start getting errors. I'm assuming that the problem is I just have to change something in it, but I don't know what. Just now getting into PowerShell so I'm probably jumping in a little over my head, but that's always the way I seem to do it.

    Here's the error:

    PS C:\Documents and Settings\mmoore3> New-Drive
    Join-Path : Cannot bind argument to parameter 'Path' because it is null.
    At C:\Documents and Settings\mmoore3\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1:25 char:29
    + $path = (join-path -path <<<< $global:profhome -childpath $alias)
    Test-Path : Cannot bind argument to parameter 'Path' because it is null.
    At C:\Documents and Settings\mmoore3\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1:26 char:20
    + if( !(Test-Path <<<< $path ) )
    New-PSDrive : Cannot bind argument to parameter 'Name' because it is an empty string.
    At C:\Documents and Settings\mmoore3\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1:41 char:22
    + new-Psdrive -name <<<< $alias -scope global -Psprovider FileSystem -root $path



    And here's how I have the profile set up.

    echo "Microsoft IIS 7.0 Environment Loader"
    echo "Copyright (C) 2006 Microsoft Corporation. All rights reserved."
    echo " Loading IIS 7.0 Managed Assemblies"

    $inetsrvDir = (join-path -path $env:windir -childPath "\system32\inetsrv\")
    Get-ChildItem -Path (join-path -path $inetsrvDir -childPath "Microsoft*.dll") | ForEach-Object {[System.Reflection.Assembly]::LoadFrom( (join-path -path $inetsrvDir -childPath $_.Name)) }

    echo " Assemblies loaded."


    ## Sign a file
    ##-------------------------------------------
    function global:Sign-Script ( [string] $file )
    {
    $cert = @(Get-ChildItem cert:\CurrentUser\My -codesigning)[0]
    Set-AuthenticodeSignature $file $cert
    }
    set-alias -name sign -value sign-script


    ## Create a Drive
    ##-------------------------------------------
    function global:New-Drive([string]$alias)
    {
    $path = (join-path -path $global:profhome -childpath $alias)
    if( !(Test-Path $path ) )
    {
    ## Create the drive's directory if it doesn't exist
    new-item -path $global:profhome -name $alias -type directory
    }
    else
    {
    ## Execute the load script for this drive if one exists
    $loadscript = (join-path -path $path -childpath "load.ps1")
    if( Test-Path $loadscript)
    {
    $load = &$loadscript
    }
    }
    # Create the drive
    new-Psdrive -name $alias -scope global -Psprovider FileSystem -root $path
    }







    # SIG # Begin signature block
    # MIIEMwYJKoZIhvcNAQcCoIIEJDCCBCACAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
    # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
    # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUuITCSlYtqvxI4scs2OMVn10m
    # EPmgggI9MIICOTCCAaagAwIBAgIQ+o6ePdhv+LRKRY6SuRmdpzAJBgUrDgMCHQUA
    # MCwxKjAoBgNVBAMTIVBvd2VyU2hlbGwgTG9jYWwgQ2VydGlmaWNhdGUgUm9vdDAe
    # Fw0xMDAxMDQyMDM1NTdaFw0zOTEyMzEyMzU5NTlaMBoxGDAWBgNVBAMTD1Bvd2Vy
    # U2hlbGwgVXNlcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAq3LAzs0BKg0E
    # no0Zd+0Rcs49pC9hcj+36Y0IEkKCvAQfBqYn+SE0GdU49LLVtpiE+i6AGAFjDfHP
    # BqyVQ3Kwh3zd4vXqbc2+3Hj3q/P/YqYfYNL/lwuesfTr3XP/9iyAbBszYJpohgaU
    # cIZVloQh7Lf2ub/uEOdrDrHdUvzKHb8CAwEAAaN2MHQwEwYDVR0lBAwwCgYIKwYB
    # BQUHAwMwXQYDVR0BBFYwVIAQSF74EB14oGkwVrjbCW95TqEuMCwxKjAoBgNVBAMT
    # IVBvd2VyU2hlbGwgTG9jYWwgQ2VydGlmaWNhdGUgUm9vdIIQFG223aBCCplPyrBx
    # nyaBozAJBgUrDgMCHQUAA4GBADD6EJfJaDC5l2fJGYinsY+F/Hk0gT5wGc8BfU76
    # GLfKJxIzv25kDfMk/e00G8EfjVulUdnLLRPuOczW4MdgIHbMzbpJfdtSloNXZnTG
    # hmrSvV83zfnJgsZx1OAJhfvQAW1Cbwr3usKA30iRYKdXi6pKaIQ+eo1mLLU+0Vqr
    # l8UDMYIBYDCCAVwCAQEwQDAsMSowKAYDVQQDEyFQb3dlclNoZWxsIExvY2FsIENl
    # cnRpZmljYXRlIFJvb3QCEPqOnj3Yb/i0SkWOkrkZnacwCQYFKw4DAhoFAKB4MBgG
    # CisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcC
    # AQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYE
    # FIyHwm/kguDG41TQ7pVPwYAVYAYEMA0GCSqGSIb3DQEBAQUABIGAogenF2VJq2Bw
    # YdcDzrEBfPzsduR48qsa1S+6Kh6NmcRG0fqLzfCkUo/XSRo5IG+uX7afjW9WMRNh
    # M2HGsvkcH72/xMtMpBQVJaa723nHoWEcCTaHznEghCxMMMgQfeJUjFkrR+TEItXg
    # z4Ae7x3F9TqlDIGTh7J9rztvRhbTE5s=
    # SIG # End signature block

  • Hey, I've run into an issue when setting up the environment. I get the following error.

    Join-Path : Cannot bind argument to parameter 'Path' because it is null.
    At C:\Documents and Settings\mmoore3\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1:25 char:29
    + $path = (join-path -path <<<< $global:profhome -childpath $alias)
    Test-Path : Cannot bind argument to parameter 'Path' because it is null.
    At C:\Documents and Settings\mmoore3\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1:26 char:20
    + if( !(Test-Path <<<< $path ) )
    New-PSDrive : Cannot bind argument to parameter 'Root' because it is null.
    At C:\Documents and Settings\mmoore3\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1:41 char:72
    + new-Psdrive -name $alias -scope global -Psprovider FileSystem -root <<<< $path


    It pops it down twice, once for each call of the New-Drive function. Here's the New-Drive function as I have it.

    ## Create a Drive
    ##-------------------------------------------
    function global:New-Drive([string]$alias)
    {
    $path = (join-path -path $global:profhome -childpath $alias)
    if( !(Test-Path $path ) )
    {
    ## Create the drive's directory if it doesn't exist
    new-item -path $global:profhome -name $alias -type directory
    }
    else
    {
    ## Execute the load script for this drive if one exists
    $loadscript = (join-path -path $path -childpath "load.ps1")
    if( Test-Path $loadscript)
    {
    $load = &$loadscript
    }
    }
    # Create the drive
    new-Psdrive -name $alias -scope global -Psprovider FileSystem -root $path
    }


    Any help is appreciated.

Comments have been disabled for this content.