Pages

Thursday, January 13, 2011

Powershell scripting

Powershell scripting

When I discovered linux for the first time in my professional development career I didn't like it very much as being a windows user I had installers, nice IDEs etc. But on linux this was a totally different story installing mono was back then very hard. But the thing I liked about linux is the very cool scripting capabilities and powerful terminal and if you will get a hang of it common tasks are super productive with it. A few years later such ting on Windows didn't exist at all but now we have Windows Powershell! :-).

Now I must say that I just recently discovered it (well I know it existed but never had the interest to use it) and it's an awesome tool, the scripting is forgiving and easy to use and the ability to write C# cmdlets is the coolest feature for me.

The first thing that I wanted to write using powershell is a file watcher script that would monitor files that my app is creating and other apps process them on a daily basis, bunch of *cs files and yes those are C# code files and the consumers are test case analysis apps.

The Code

Let us look at the full code and then explain it to learn how to solve some common problems that beginner (like me) needs to solve when starting powershell scripting.

function watch
{
    param($path,$filter)
    
    $action = {
        $now =[datetime]::now
        Write-Host "$now $($eventArgs.ChangeType) file $($eventArgs.Name) " 
    }

    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = "$path"
    $watcher.Filter = "$filter"
    $watcher.IncludeSubdirectories = $true
    $watcher.EnableRaisingEvents = $true
    $watcher.NotifyFilter = 
    [System.IO.NotifyFilters]::LastAccess -bor
    [System.IO.NotifyFilters]::LastWrite -bor
    [System.IO.NotifyFilters]::FileName

    $watcher
    Register-ObjectEvent $watcher "Changed" -Action  $action 
    Register-ObjectEvent $watcher "Created" -Action $action 
    Register-ObjectEvent $watcher "Deleted" -Action  $action 
    Register-ObjectEvent $watcher "Renamed" -Action  $action 
}

The script uses a simple class from .NET called FilleSystemWatcher and then utilizes it to create propper events for operations on a directory and it's sub directories, so nothing to fancy.

param($path,$filter)

When defining a function with parameters one needs to specify them in the param section for this to work this needs to be the first instruction within the brackets.

$action = {
        $now = $start_date=[datetime]::now
        Write-Host "$now $($eventArgs.ChangeType) file $($eventArgs.Name) " 
    }

This is an anonymous function delegate (we could say so) and it's used for all of the events in the watcher, inside we use the eventArgs to determine who did what and use DateTime to get the time, the [class]::propertyOrFunction notation is used to access NET classes, we could also get the date by using a build in script command.

$now = Get-Date

but keep in mind that this is a bit slower then the NET one and if the folder will have lots of traffic this could have an impact.

$watcher = New-Object System.IO.FileSystemWatcher

This is just a paramteress constructor call to the NET object, we could also specify the parameters here by using brackets: New-Object System.IO.Something($param1,$param2)

$watcher.NotifyFilter = 
    [System.IO.NotifyFilters]::LastAccess -bor
    [System.IO.NotifyFilters]::LastWrite -bor
    [System.IO.NotifyFilters]::FileName

Watcher has a flag enumeration for filters so in order for the watch to works as intended we need to combine some enums and this is done by using the -bor and this means binary or "|"

$watcher
    Register-ObjectEvent $watcher "Changed" -Action  $action 
    Register-ObjectEvent $watcher "Created" -Action $action 
    Register-ObjectEvent $watcher "Deleted" -Action  $action 
    Register-ObjectEvent $watcher "Renamed" -Action  $action

Here we register the events for the watcher, nothing special... duh

Testing

Now if we run the function:

PS C:\Users\Vizzard>watch "C:\" "*.*"

We get Output:

PS C:\Users\Vizzard>
PS C:\Users\Vizzard> 01/13/2011 21:53:32 Changed file f.cs
01/13/2011 21:53:32 Changed file f.cs
01/13/2011 22:41:50 Deleted file f31a3b44-f0f9-4a4e-a457-0a4e98998d41.cs
...

(please don't mind the stupid file names :-D )

By copying the script file into the $PsHome directory it can be executed just by specifying the command, so no need to specify the script file path.

Things to improve:

This is handy dandy and all, but as we monitor interesting events we should log them into file and not only on the console output stream. Our watcher would be more useful if it could monitor if someone has opened the file but this requires calls to WINApi so we would need to use "Invoke-Win32" function.

No comments:

 
ranktrackr.net