Tag Archives: Powershell

Powershell: Selecting files that don’t contain specified content

I had a quick task today that required that I search a couple hundred directories and look in specific files to see if they contained a certain piece of code. All I wanted was the list of files that did not. PowerShell to the rescue!

I’ll break down each of the calls I used and provide the full command-line at the end for anyone interested.

First, we need to recurse through all the files in a specific directory. This is simple enough using Get-ChildItem.

Get-ChildItem -include [paths,and,filenames,go,here] -recurse

Next, I needed to loop through each of those files using ForEach-Object :

ForEach-Object { … }

Next, I need to get the content of each of those files. That’s done using Get-Content:

Get-Content [file]

I then need to be able to determine if the contents of the file contains the string I’m looking for. This is easy enough with Select-String:

Select-String -pattern [pattern]

That’s pretty much all the commands you need to know — we just need to put them together now.

PS> Get-ChildItem -include *.html,*.xhtml -recurse |
    ForEach-Object { if( !( select-string -pattern "pattern" -path $_.FullName) ) 
                     { $_.FullName}}

Reading IIS.NET Blogs (or any RSS) 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:CurrentUserMy -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 exploreriexplore.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

Extending IIS 7 APIs through PowerShell (Part II)

In my previous post, I showed you how easy it was to leverage your knowledge of the IIS 7 managed SDK in Windows PowerShell.  We loaded the IIS 7 managed assemblies and then traversed the object model to display site information and stop application pools.  While this in itself was pretty cool, I don’t think I quite got my point across about how powerful IIS 7 and PowerShell are together. As such, I wanted to show you some more fun things to do with PowerShell in the name of easy IIS 7 administration.

First, our examples still required a great deal of typing and piping and filtering.  Let’s modify our profile script from my previous post by adding at least one new global variable that will give us access to the ServerManager without much typing.  Add the following line to your profile script from my previous post.

new-variable iismgr -value (New-Object Microsoft.Web.Administration.ServerManager) -scope "global"

(if you don’t have a profile script yet, go back to my previous post to learn how to create one).

Note: If you signed your script before, you’ll have to do it again after modifying the script

Open a new instance of PowerShell and now you can access the site collection just by typing:

PS C:> $iismgr.Sites

 That’s considerably smaller than our previous examples.  But let’s not stop there.  What happens if I want to search the site collection? PowerShell has some fun syntax for this as well. I simply pipe the output of my SiteCollection to a “Where-Object” cmdlet and then specify what site I’m looking for:

$iismgr.Sites | Where-Object {$_.Name -match "Default*"}

This is still quite a bit of typing when all I really want to do is find the default website. You may ask “Wouldn’t it be easier if we could just add a “Find” method to the SiteCollection object?” Well I’m glad you asked *cough*! Next, we are going to do just that! Open up another instance of notepad and add the following XML to it:


<?xml version="1.0" encoding="utf-8" ?>
<!-- *******************************************************************
Copyright (c) Microsoft Corporation.  All rights reserved.
 
THIS SAMPLE CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY 
OF ANY KIND,WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE. IF THIS CODE AND INFORMATION IS MODIFIED, THE ENTIRE RISK OF USE
OR RESULTS IN CONNECTION WITH THE USE OF THIS CODE AND INFORMATION 
REMAINS WITH THE USER.
******************************************************************** -->
<Types>
  <Type>
    <Name>Microsoft.Web.Administration.SiteCollection</Name>
      <Members>
        <ScriptMethod>
          <Name>Find</Name>
          <Script>
          $rtr = "";
          if ( $args[0] ) 
          { $name = $args[0];
            $rtr = ((New-Object Microsoft.Web.Administration.ServerManager).Sites | 
                     Where-Object {$_.Name -match $name}) 
          } 
          else 
          { 
            $rtr = "No sites found." 
          };
          $rtr
          </Script>
        </ScriptMethod>
      </Members>
  </Type>
</Types>

What we’ve done is identified that we want to add a scripted method to the Microsoft.Web.Administration.SiteCollection object. In our case, I’ve added a “Find” method by using the same cmdlet t at we typed before to search the site collection. The difference is, this type I use the $args array variable to check for a parameter and use it if one is available. Now, save this file into your %windir%system32WindowsPowerShellv1.0 directory as “iis.types.ps1xml”. Once you’ve saved the file, sign it the same way you signed your profile script. Keep in mind that these xml files contain code, so signing your xml is required to keep your PowerShell experience a secure one. Now, open your profile script (again) and add the following lines to the end:


new-variable iissites -value (New-Object Microsoft.Web.Administration.ServerManager).Sites -scope "global"
new-variable iisapppools -value (New-Object Microsoft.Web.Administration.ServerManager).ApplicationPools -scope "global"
update-typedata -append (join-path -path $PSHome -childPath "iis.types.ps1xml")

Note: Once again, you’ll have to re-sign this profile if your execution policy requires signed scripts.

Notice that I added two more variables: $iissites and $iisapppools. These variables allow me to access the site collection and application pool collection with a simple keyword. Lets try them out in PowerShell. Make sure you open a new instance of PowerShell so your profile script and xml type data are updated properly. Once your new instance of PowerShell is open, type the following:

PS C:> $iissites.Find("^Default*")

PowerShell will do all the work for you and you have MUCH less typing to do.

Another alternative to using xml files is to simply create a function and add it to your profile. For instance, we can create a function called “findsite” that provides the same functionality as our previous example. Either type the following command into PowerShell or add it to your profile script:

PS C:> function findsite { $name=$args[0]; ((New-Object Microsoft.Web.Administration.ServerManager).Sites | Where-Object {$_.Name -match $name}); } }

Now you can search for a site using the following syntax:

PS C:> findsite default*

Whatever way we choose to extend Microsoft.Web.Administration and/or PowerShell, we can use our output as we did before:

PS C:> (findsite default*).Id

The previous line should display the Id of the default web site. We can also stop the website:

PS C:> (findsite default*).Stop()

We can keep taking this to extremes and truncate every operation that we perform on a semi-regular basis. These scripts are not one-offs. Each script function we create or append to an existing object model can be reused and piped as input to another function. The possibilities are endless and completely customizable to your needs.

Accessing IIS 7 APIs through PowerShell (Part I)

I’ve caught the PowerShell bug. In between stints with my ever-expanding code samples, I play with PowerShell a lot.  I thought I’d share a quick example of how to load Microsoft.Web.Administration.dll and use it to perform some basic tasks.

Note: I’m running these samples on Windows Vista RTM, but I have no reason to believe this will not work on the PowerShell release candidates for the Vista RC* builds that are available now

So let’s get started.

First, PowerShell has no idea where Microsoft.Web.Administration.DLL is so you have to tell it how to load it. Anyone who has written code to dynamically load an assembly should be familiar with this syntax.  Type the following command

PS C:> [System.Reflection.Assembly]::LoadFrom( “C:windowssystem32inetsrvMicrosoft.Web.Administration.dll” )

The path to your assembly may change depending on your install.  I’ll show you later how to use environment variables to calculate the correct path.  In the mean time the out put of the line above display something like the following:

GAC  Version    Location
---  -------    --------
True v2.0.50727 C:WindowsassemblyGAC_MSILMicrosoft.Web.Administration 7.0.0.0__31bf3856ad364e35Microsoft . . .

Once the assembly is loaded you can use PowerShell’s “New-Object” command to create a ServerManager object that is defined in Microsoft.Web.Administration.

PS C:> (New-Object Microsoft.Web.Administration.ServerManager)

This doesn’t give you much except the list of properties the ServerManager exposes:

ApplicationDefaults      : Microsoft.Web.Administration.ApplicationDefaults
ApplicationPoolDefaults  :
ApplicationPools         :
SiteDefaults             : Microsoft.Web.Administration.SiteDefaults
Sites                    : {Default Web Site}
VirtualDirectoryDefaults : Microsoft.Web.Administration.VirtualDirectoryDefaults
WorkerProcesses          : {}

To get more detail, you need to use the properties and methods of the ServerManager object to drill down and get the information we want. The ServerManager provides access to all of the sites on your machine through a SiteCollection object. This SiteCollection is made available through the “Sites” property of the ServerManager. 

PS C:> (New-Object Microsoft.Web.Administration.ServerManager).Sites

Which will produce a list view of all the sites and their associated property names/values.

ApplicationDefaults        : Microsoft.Web.Administration.ApplicationDefaults
Applications               : {DefaultAppPool, Classic .NET AppPool}
Bindings                   : {}
Id                         : 1
Limits                     : Microsoft.Web.Administration.SiteLimits
LogFile                    : Microsoft.Web.Administration.SiteLogFile
Name                       : Default Web Site
ServerAutoStart            : True
State                      : Started
TraceFailedRequestsLogging : Microsoft.Web.Administration.SiteTraceFailedRequestsLogging
VirtualDirectoryDefaults   : Microsoft.Web.Administration.VirtualDirectoryDefaults
ElementTagName             : site
IsLocallyStored            : True
RawAttributes              : {name, id, serverAutoStart}
ApplicationDefaults        : Microsoft.Web.Administration.ApplicationDefaults
Applications               : {DefaultAppPool}
Bindings                   : {}
Id                         : 2
Limits                     : Microsoft.Web.Administration.SiteLimits
LogFile                    : Microsoft.Web.Administration.SiteLogFile
Name                       : Test Web Site 1
ServerAutoStart            : False
State                      : Stopped
TraceFailedRequestsLogging : Microsoft.Web.Administration.SiteTraceFailedRequestsLogging
VirtualDirectoryDefaults   : Microsoft.Web.Administration.VirtualDirectoryDefaults
ElementTagName             : site
IsLocallyStored            : True
RawAttributes              : {name, id, serverAutoStart}

Of course, this isn’t the easiest view to read, so let’s say we just list the site names by piping our site list to the “ForEach-Object” command in PowerShell and display a list of site names only:

 PS C:> (New-Object Microsoft.Web.Administration.ServerManager).Sites | ForEach-Object {$_.Name}

This looks much more concise:

Default Web Site
Test Web Site 1

We could also use the Select-Object syntax to query the list into a table format:

 PS C:> (New-Object Microsoft.Web.Administration.ServerManager).Sites | Select Id, Name
        Id Name
        -- ----
         1 Default Web Site
         2 Test Web Site 1

Now lets use PowerShell to manage application pools. We can fit several commands on one line by using the semi-colon.  The following command-line is actually four different operations: Storing the application pool collection into a variable, displaying the name and runtime status of the first application pool, stopping the first application pool, then displaying the name and status again.

PS C:> $pools=(New-Object Microsoft.Web.Administration.ServerManager).ApplicationPools; $pools.Item(0) | Select Name, State;$pools.Item(0).Stop(); $pools.Item(0) | Select Name, State

Running this sample should display the following:

Name                                         State
----                                         -----
DefaultAppPool                               Started
Stopped
DefaultAppPool                               Stopped

This is nice, but we can do this already with appcmd.exe right? Well, to some extent.  We don’t get the features of PowerShell that allow us to format our output the data to our liking. Also, as a developer, I find it much easier to use the API syntax I’m already familiar with than to remember appcmd.exe syntax.  Furthermore, PowerShell allows us to use WMI alongside our managed code calls, and unlike appcmd.exe, you can extend PowerShell and cmdlets. PowerShell gives you the ability to easily manage multiple servers from one command prompt on one machine.  Watch the PowerShell/IIS 7 interview on Channel9 if you want to see this remote administration in action.

One last thing that PowerShell brings to the table is the ability to “spot-weld” our object models (as Scott Hanselman quipped). You can create/modify/extend type data and formatting to your hearts desire.  For more information on this, check out the PowerShell documentation found in the PowerShell install, or in the PowerShell documentation set.

So, I would be remiss in this post if I didn’t try to make your PowerShell / IIS 7.0 life easier.  As such, I’ve created a profile script that loads all the IIS 7.0 managed assemblies for you.  The script is simple and contains more  echo commands than actual working script lines.

To install this script run the following command inside PowerShell:

PS C:> if ( test-path $profile ) { echo “Path exists.” } else { new-item -path $profile -itemtype file -force }; notepad $profile

This will create a profile path for you if you don’t already have one, then open up your profile in notepad.  If you haven’t added anything to the file, it will obviously display an empty file.  Paste the following in notepad when it opens:

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 “system32inetsrv”)
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.”

Now, save the profile and close notepad.  You will likely have to sign this script or change your script execution policy to something very weak to make this script run properly (obviously I’m not recommending the latter). To find out more about signing scripts, type “get-help about_signing” in PowerShell. The instructions to create a self-signed certificate found in that help file are as follows:

In an SDK Command Prompt window, run the following commands.
The first command creates a local certificate authority for your computer.
The second command generates a personal certificate from the certificate authority:

makecert -n "CN=PowerShell Local Certificate Root" -a sha1 `
   -eku 1.3.6.1.5.5.7.3.3 -r -sv root.pvk root.cer `
   -ss Root -sr localMachine
makecert -pe -n "CN=PowerShell User" -ss MY -a sha1 `
   -eku 1.3.6.1.5.5.7.3.3 -iv root.pvk -ic root.cer
   MakeCert will prompt you for a private key password.

Go ahead and make a certificate for yourself following those instructions. To sign your profile, within PowerShell type the following:

PS C:>Set-AuthenticodeSignature $profile @(get-childitem cert:CurrentUserMy -codesigning)[0]

So far, you’ve created a certificate and signed your script. Now, you will have to change your script execution policy down at least one level from the default.  The default doesn’t allow scripts at all.  To get scripts to execute, at the minimum you’ll have to set it to “AllSigned” to allow only signed scripts to execute.  In this mode, each time you execute a script from a new publisher, you’ll be asked what level of trust to assign to the publisher (unless you respond to the prompt to “Always Run” or “Never Run” scripts from that publisher)

Do you want to run software from this untrusted publisher?
File C:UsersTobinTDocumentsWindowsPowerShellMicrosoft.PowerShell_profile.ps1 is published by CN=PowerShell User
and is not trusted on your system. Only run scripts from trusted publishers.
[V] Never run  [D] Do not run  [R] Run once  [A] Always run  [?] Help (default is "D"):

Now, each new instance of PowerShell that you run will automatically load the IIS 7.0 managed assemblies.  I know it seems like a great deal of work, but it really isn’t once you’ve made a few rounds around inside PowerShell. Consider that you only have to create the script once and then you have full the full range of the managed IIS 7.0 SDK at your fingertips inside PowerShell. 

If you have problems, feel free to leave comments and I’ll do my best to help you.