November 2009 - Posts

Windows 7 – Pet Hate #1

I’ve been using Windows 7 since January and as I’ve reported several times I have been really happy with it.  One source of irritation has arisen – the Action Center.

This is a reporting system for system issues such as security (firewall turned off) or maintenance (backups not configured).  This is fine but the thing takes over. Once it has popped its window up to say there is a problem it won’t go away. Not good.

I don’t mind been reminded of things but not to the point where I am then effectively stopped working because these windows are configured to always remain on top.

Technorati Tags: ,
Posted by RichardSiddaway | with no comments
Filed under:

WMICookbook: Read Routing Table

When we need to troubleshoot networking problems we will sometimes need to read the routing table on a machine. The routing table contains the information on the routes known to the network interfaces. This can be created automatically or manually . On the local machine we can use the route command to find this information – but how do we find it on a remote machine. WMI has a class that enables us to read the routing table.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
function Get-RouteTable {
param (
    [parameter(ValueFromPipeline=$true)]
    [string]$computer="."
)

## create class for object
$source=@"
public class WmiIPRoute
{
    private string _destination;
    private string _mask;
    private string _nexthop;
    private string _interface;
    private int _metric;
   
     public string Destination {
        get {return _destination;}
        set {_destination = value;}
    }
   
    public string Mask {
        get {return _mask;}
        set {_mask = value;}
    }
   
    public string NextHop {
        get {return _nexthop;}
        set {_nexthop = value;}
    }
   
    public string Interface {
        get {return _interface;}
        set {_interface = value;}
    }
   
    public int Metric {
        get {return _metric;}
        set {_metric = value;}
    }
}
"@

Add-Type -TypeDefinition $source

    $data = @()
    Get-WmiObject -Class Win32_IP4RouteTable -ComputerName $computer| foreach {
        $route = New-Object -TypeName WmiIPRoute
        $route.Destination = $_.Destination
        $route.Mask        = $_.Mask
        $route.NextHop     = $_.NextHop
        $route.Metric      = $_.Metric1
       
        $filt = "InterfaceIndex='" + $_.InterfaceIndex + "'" 
        $ip = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter $filt -ComputerName $computer).IPAddress

        if ($_.InterfaceIndex -eq 1) {$route.Interface = "127.0.0.1"}
        elseif ($ip.length -eq 2){$route.Interface = $ip[0]}
        else {$route.Interface = $ip}
       
        $data += $route
    }
    $data | Format-Table -AutoSize 
}

 

Our function takes a single parameter – a computer name (or IP address) I’ve used the advanced function parameters to this function operates on the pipeline.  We then create a .NET class to hold our data – we will be accessing a couple of WMI classes so we’ll make the presentation neat.  The class is added using Add-Type.

As an aside I really like this technique for collecting data together into a single object.  Its neater and easier to use than Add-Member.

We can then use Get-WmiObject -Class Win32_IP4RouteTable -ComputerName $computer to retrieve the routing information. We create an instance of our object and populate the properties.  One thing we need to know is the Interface ie which address on our machine is using this route,  We can find this from the Win32_NetworkAdapterConfiguration  class.  There isn’t an association but we can find the address by using the InterfaceIndex as a filter – its the same value in both classes.  if the InterfaceIndex = 1 its the Loopback Adapter on 127.0.0.1

We can then add our route to the data. When all the routes are collected we can display the data.  The data could be output onto the pipeline but at the moment I can’t think what else to do with it so we’ll leave it like this for now.

Note: Win32_IP4RouteTable is only available on Windows 2003 and later

Excel 2010 beta

 

The beta still allows those of us that aren’t in the USA to use the simple method of creating a new spreadsheet using PowerShell.

001
002
003
$xl = New-Object -comobject "excel.application" 
$xl.visible = $true
$xlbooks =$xl.workbooks.Add()

 

Makes life easier.

In case you are wondering – if you don’t have your machine set to US English and you are using Excel 2007 or earlier you have to do this.

001
002
003
004
005
$xl = New-Object -comobject "excel.application" 
$xl.visible = $true
$xlbooks =$xl.workbooks
$newci = [System.Globalization.CultureInfo]"en-US"
$xlbooks.PSBase.GetType().InvokeMember("Add", [Reflection.BindingFlags]::InvokeMethod, $null, $xlbooks, $null, $newci)

 

Posted by RichardSiddaway | with no comments
Filed under: ,

PowerShell v2 cmdlets

Jonathan has started a series of posts at http://www.jonathanmedd.net/ on all of the new cmdlets in PowerShell v2.  If you are just starting with v2, or want more information on whats new, this is a good place to start.

Technorati Tags: ,
Posted by RichardSiddaway | with no comments
Filed under:

Office 2010 – fixed in beta

A couple of issues that I noticed with the Office 2010 TP have been fixed in the beta:

  • Word documents will now download from the Internet and open
  • One undocks back to normal view correctly

I did notice that in the Visio 2010 beta the Window title still says Technical Preview.  All the other components just have the application name.

Technorati Tags:
Posted by RichardSiddaway | with no comments
Filed under:

Reminders via WPF

If I am working on my home machine I don’t necessarily have Outlook or any other application that gives me calendaring capability open. There are times when I need a simple reminder to do something. For some reason I always seem to have PowerShell open so I thought of using the eventing system to give me a reminder.  I could also do this via the task scheduler functions in the PowerShellPack  (Windows 7 Resource kit) which I’ll look at another day.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
function Set-Alarm {
param (
        [datetime]$time,
       
        [string]$msg = "Alert Issued"
      )     

    $now = Get-Date 
    if ($time -gt $now) {$ts = $time - $now}
    else {throw "Time has to be in future"}
       
    $timer = New-Object -TypeName System.Timers.Timer
    $timer.Interval = $ts.TotalMilliseconds
    $timer.AutoReset = $false
    $timer.Enabled = $true
   
    $global:act = "Start-Process powershell -ArgumentList ""-Sta -WindowStyle Hidden -File C:\Scripts\WPF\show-alert.ps1 """"$msg"""" "" "
   
    Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier TimeAlert  -Action {Invoke-Expression -Command $act }
}

 

My function accepts a time and a message

Set-Alarm "18:47" "Test1"

It then gets the current time, compares the two times and assuming the alert is to be issued in the future creates a Timespan object be subtracting the times as shown.

We can then create  .NET timer object and set the interval to the total number of milliseconds in our timespan. We only want it to fire once so we set autoReset to false and then enable the timer.

I then create a global variable containing the powershell start up commands.  In this case I want it to start in Single Thread mode so I can use the WPF classes.  I call a script when PowerShell starts and pass the script the message.  Note the number of quotes around the $msg variable – this is to make sure the string passed to invoke-expression is correct.  This is messy but needed.

The $act variable has to be global because the action scriptblock for Register-objectevent isn’t evaluated until the event fires.  If $act is in the script scope it won’t be found and the event won’t fire correctly.

 

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
param (
[string]$msg = "Testing",
[string]$title = "Alert"
)
## load WPF assemblies
Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase

## create a window
$window = New-Object -TypeName System.Windows.Window
$window.Title = $title
$window.Content = $msg
$window.FontSize = 36
$window.SizeToContent = "WidthAndHeight"

## display window
$null = $window.ShowDialog()

 

The script loads the WPF assemblies I need and then creates a window and writes out the message thats been passed in.

This is a bit messy with having to create a global variable but I can’t think of a simpler way to access the variable in the scriptblock for Register-objectevent. The other issue is that I can only have a single event of this type defined because of the variable.  I would need to create the variable with a random name and create another string of the Register-objectevent  invocation.

Technorati Tags: ,,
Posted by RichardSiddaway | with no comments

WMI CookBook: WMI Presentation

As with other PowerShell objects there is a default format for the display of WMI objetcs. If we look at the NetworkAdapter class

PS> Get-WmiObject -Class Win32_NetworkAdapter -Filter "DeviceId='11'"

ServiceName      : athr
MACAddress       : 00:00:00:00:00:00
AdapterType      : Ethernet 802.3
DeviceID         : 11
Name             : Atheros AR5007 802.11b/g WiFi Adapter
NetworkAddresses :
Speed            : 54000000

You didn’t think I’d really give you my MAC address did you?

If we want to see all of the properties we can do this

Get-WmiObject -Class Win32_NetworkAdapter -Filter "DeviceId='11'" | select -Property *

but we get a few ugly looking entries

__GENUS
__CLASS
__SUPERCLASS
__DYNASTY
__RELPATH
__PROPERTY_COUNT
__DERIVATION
__SERVER
__NAMESPACE
__PATH

that we may not actually want or need.

We can easily filter them out if desired

Get-WmiObject -Class Win32_NetworkAdapter -Filter "DeviceId='11'" | select -Property * -ExcludeProperty "__*"

The ExcludeProperty takes a wild card to identify the properties with two underscore characters at the front.

Simple but effective.

Posted by RichardSiddaway | with no comments

Office 2010 beta

The Office 2010 beta is available on TechNet and MSDN. The installation seems very smooth. Need to uninstall Technical Preview version first – thats always boring and a pain.

Slight hiccup over activation as the license key wasn’t asked for by Office when doing a customised install!!  Soon resolved that.  Visio and Project have separate installs but the look and feel matches the rest of Office.

Now I’ve got that I’ll try a few more PowerShell and Office projects.  Be good to dig out the One Note PowerShell functions as well.

I remembered to export my autocorrect entries before uninstalling the previous version and managed to import them back in – functions to do this can be found in earlier posts.

I’m expecting this to be boring & just work.  I’ll let you know how I get on.

Technorati Tags: ,,
Posted by RichardSiddaway | with no comments
Filed under:

WMI CookBook: Associators Pt I

WMI is a wonderful thing and like many people I have a love-hate relationship with it. It is incredibly powerful, and can take you deep into the system, but it is not easy to find your way.

I have decided to spend some time digging into WMI and will document the findings in a (long) series of blog posts. I also intend to pull the information together into a document that will be available for download.  The scripts will end up in PowerShell modules that will also be available for download.

I am doing this with PowerShell v2 on Windows 7. Where necessary, and wherever possible, I will give alternatives that can be used with PowerShell v1.

The main trick with WMI is finding out the class or classes we need to actually use. The standard PowerShell way of searching for classes is to use the –List parameter.  Network Adapters are common items we need to deal with so we’ll see what WMI can tell us.  First, what WMI classes deal with Network Adapters. One thing we can do with –List is give a partial class name and a wildcard to restrict the results (otherwise we get lots, and lots of classes returned).  To begin with I’m only interested in the name of the class. I’m working in the default name space so don’t need to mention it.

PS> Get-WmiObject -List "Win32_NetworkAdapter*" | Select Name

Name
----
Win32_NetworkAdapter
Win32_NetworkAdapterConfiguration
Win32_NetworkAdapterSetting

We could now go off to MSDN and check what these classes do but we can find that information directly.  As Jeffrey showed recently http://blogs.msdn.com/powershell/archive/2009/08/30/exploring-wmi-with-powershell-v2.aspx we can use the –Amended parameter to show the description property.

001
002
003
Get-WmiObject -List "Win32_NetworkAdapter*" | foreach {
    (( Get-WmiObject -List $_.Name  -Amended ).Qualifiers | Where {$_.Name -eq "Description"}).Value
}

 

This gives use this information for the three classes.

The Win32_NetworkAdapter class represents a network adapter on a Win32 system.
The Win32_NetworkAdapterConfiguration class represents the attributes and behaviors of a network adapter. This class has been extended to include extra properties and methods that support the management of the TCP/IPprotocols (and are independent of the network adapter).
The Win32_NetworkAdapterSetting class represents an association between a network adapter and its configuration settings.

If we look at Win32_NetworkAdapterSetting

Get-WmiObject Win32_NetworkAdapterSetting

we will see an entry for each adapter that links the adapter with its configuration. This information is paired like this:

Element          : \\RSLAPTOP01\root\cimv2:Win32_NetworkAdapter.DeviceID="11"
Setting          : \\RSLAPTOP01\root\cimv2:Win32_NetworkAdapterConfiguration.Index=11

If we look at the adapter and its configuration

Get-WmiObject -Class Win32_NetworkAdapter -Filter "DeviceID='11'"

Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "Index='11'"

we can see the individual information.  Going through the Win32_NetworkAdapterSetting class is tedious but now we now that the adapter’s DeviceId is linked to the Configurations Index we can use this ASSOCIATION via a bit of WQL.

Get-WmiObject -Query "ASSOCIATORS OF {Win32_NetworkAdapter.DeviceID='11'} WHERE ResultClass=Win32_NetworkAdapterConfiguration"

DHCPEnabled      : True
IPAddress        : {192.168.196.138, fe80::6d95:b824:6a72:a0a9}
DefaultIPGateway : {192.168.196.1}
DNSDomain        :
ServiceName      : athr
Description      : Atheros AR5007 802.11b/g WiFi Adapter
Index            : 11

The other thing to think about is what other associations exist.

PS> Get-WmiObject -Query "ASSOCIATORS OF {Win32_NetworkAdapter.DeviceID='11'} " | Select __CLASS -Unique

__CLASS
-------
Win32_PnPEntity
Win32_ComputerSystem
Win32_NetworkAdapterConfiguration
Win32_IRQResource
Win32_DeviceMemoryAddress
Win32_NetworkProtocol
Win32_SystemDriver

So we have a set of classes that are all associated through the network adpater device id.   In the next few posts we will dig into this web and see what we can discover and how we can use these associations to help us understand our system.

Technorati Tags: ,,

Office 2010 TP

Recent talk of the imminent arrival of the Office 2010 beta made me suddenly remember I have been using the Technology Preview for a number of months. The only issue I have found is when downloading Word documents from the web.  Apart from that its solid.  So solid I forgot it was a Technology preview and just used it!!

Assuming the beta fixes the problem and is just as solid its going to be good.

Technorati Tags:
Posted by RichardSiddaway | with no comments
Filed under:

Patch Tuesday

Patch Tuesday – the second Tuesday in the month – is the day Microsoft (and other vendors) release their patches. If you want to be able to plan ahead for these dates ( like arranging holiday or sick leave ) these two functions will supply the dates to look out for.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
function get-secondTuesday {
param([datetime]$date)
    switch ($date.DayOfWeek){
        "Sunday"    {$patchTuesday = $date.AddDays(9); break} 
        "Monday"    {$patchTuesday = $date.AddDays(8); break} 
        "Tuesday"   {$patchTuesday = $date.AddDays(7); break} 
        "Wednesday" {$patchTuesday = $date.AddDays(13); break} 
        "Thursday"  {$patchTuesday = $date.AddDays(12); break} 
        "Friday"    {$patchTuesday = $date.AddDays(11); break} 
        "Saturday"  {$patchTuesday = $date.AddDays(10); break} 
     }
     $patchTuesday.ToLongDateString()
}

function Get-PatchTuesday {
param (
    [parameter(ValueFromPipeline=$true)]
    [int]$year = (Get-Date).Year,
   
    [switch]$nextmonth
    )
    
    if ($nextmonth){
        $now = Get-Date
        $d = Get-Date -Day 1 -Month $($now.Month + 1) -Year $now.Year
        get-secondTuesday $d
    }
    else {
        1..12 | foreach {
            $d = [datetime]"$_/1/$year"
            get-secondTuesday $d
        }   
    }
}

 

Get-PatchTuesday takes a year (default is current year) and works out the first of each month as a date. This is passed to get-secondtuesday to calculate the date the patches will be released.

If the –nextmonth parameter is used then the first of next month is calculated and the patch date for that month retrieved from getsecondtuesday.

Dates for the next few months are

08 December 2009
12 January 2010
09 February 2010
09 March 2010
13 April 2010
11 May 2010
08 June 2010
13 July 2010
10 August 2010
14 September 2010
12 October 2010
09 November 2010
14 December 2010

Time to start planning.

These functions can be adapted to provide dates for any date that follows the pattern of nth weekday.

Technorati Tags: ,,
Posted by RichardSiddaway | with no comments
Filed under:

Friday 13th Part II

 

No not the film

001
002
003
004
005
006
007
008
009
010
011
012
013
function get-fri13 {
<# .SYNOPSIS Calculates each Friday 13th for a given year .DESCRIPTION .PARAMETER year Specifies the year as an integer .EXAMPLE for current year get-fri13 for single year get-fr13 2010 for multiple years 2010..2020 | foreach {get-fri13 $_} .INPUTS Year as Integer #>
param (
    [parameter(ValueFromPipeline=$true)]
    [int]$year = (Get-Date).Year
    )
    1..12 | foreach {
        $d = [datetime]"$_/13/$year"
        if ($d.DayOfWeek -eq "Friday"){
            $d.ToLongDateString()
        }
    }
}

 

With the last friday 13th out of the way for this year I thought I’d convert my script into a function while I was seeing what next year brings.  Its the first function in my datetime module and I’ve added some comment based help.

One quick point – if you use mandatory=$true in the parameter block it overrides the default value and expects a value to be given

Technorati Tags: ,
Posted by RichardSiddaway | with no comments
Filed under:

Scheduled Tasks

Keeping on the theme of Scheduled Tasks I wanted to dig into the tasks that exist on my system.  I did a fresh install of Windows 7 in August and haven’t created any scheduled tasks – so what I see should be close to the system defaults.  This machine isn’t in a domain.

I can load the task scheduler functions

PS> Import-Module TaskScheduler
PS> get-command -Module TaskScheduler

CommandType     Name
-----------     ----
Function        Add-TaskAction
Function        Add-TaskTrigger
Function        Connect-ToTaskScheduler
Function        Get-RunningTask
Function        Get-ScheduledTask
Function        New-Task
Function        Register-ScheduledTask
Function        Remove-Task
Function        Start-Task
Function        Stop-Task

I can see the tasks that are running

PS> Get-RunningTask

Name          : SystemSoundsService
InstanceGuid  : {9E499B76-8A4E-4215-B043-619B0403D8F6}
Path          : \Microsoft\Windows\Multimedia\SystemSoundsService
State         : 4
CurrentAction : Microsoft PlaySoundService Class
EnginePID     : 2456

in this case the definition is empty so I can’t see exactly what it is doing though the path and name give a strong hint its related to the sound system.

Scheduled tasks are arranged in a folder hierarchy.   If you want to see the full list use

Get-ScheduledTask –Recurse

we see a large number of jobs that are either disabled, ready or running.

If you compare the list with the Task scheduler GUI tool you will see some jobs are missing.  These can be viewed by using the hidden switch

Get-ScheduledTask -Recurse -Hidden | select path, name

It is also possible to directly supply a folder and view the jobs in there.

Looking at the total job population

PS> Get-ScheduledTask -Recurse -Hidden | group status

Count Name                     
----- ----                    
   57 Ready                     
   20 Disabled                  
    2 Running                   
    1 Queued
                    

The Ready jobs look interesting

Get-ScheduledTask -recurse -hidden | where {$_.status -eq "Ready" -and $_.LastRunTime -gt $((get-date).AddMonths(-1))} | sort LastRunTime -Descending

will show everything run in the last month that is ready to run again.  Surprising just how many back ground tasks occur.

Next time we’ll look at setting up a task to run when we login.

Cleaning the Temp folder - Scheduling

When I posted about cleaning the temp folder http://msmvps.com/blogs/richardsiddaway/archive/2009/11/04/cleaning-temp-folder.aspx I said I would look at scheduling the task.  I had intended to use the TaskScheduler module in the new PowerShell pack which can be downloaded from http://code.msdn.microsoft.com/PowerShellPack.  It is also available in the Windows 7 Resource Kit.  The pack provides 10 modules for PowerShell 2.0 covering a number of areas:

DotNet
FileSystem
IsePack
PowerShellPack
PSCodeGen
PSImageTools
PSRSS
PSSystemTools
PSUserTools
TaskScheduler
WPK

The modules are installed in WindowsPowerShell\Modules in your profile.

I’ll look at some of the others in future posts but for now I want to concentrate on the Scheduler.  The module supplies a number of functions

Add-TaskAction
Add-TaskTrigger
Connect-ToTaskScheduler
Get-RunningTask
Get-ScheduledTask
New-Task
Register-ScheduledTask
Remove-Task
Start-Task
Stop-Task

I had difficulty with the Add-TaskAction and Add-TaskTrigger functions so I ended up experimenting and produced the following script

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
Import-Module TaskScheduler
$task = New-Task

$at = [datetime]"1:00 PM"
## trigger on daily schedule
$trigger = $task.Triggers.Create(2)
$trigger.StartBoundary = $at.ToString("s")
$trigger.DaysInterval = 1
$task

## run command line operation
$Action = $Task.Actions.Create(0)
$action.Path = Join-Path $psHome "PowerShell.exe"
$action.WorkingDirectory = $pwd

$Script = [ScriptBlock]{Import-Module FileFunctions; Remove-TempContents}
$encodedScriptBlock = $encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))
$action.Arguments+= " -encodedCommand $encodedCommand"
$task

$scheduler = Connect-ToTaskScheduler
$scheduler.Connected

$folder = $scheduler.GetFolder("")
## arguments task name, task definition, create task, userid, password, user must be logged in, security descriptor
$folder.RegisterTaskDefinition("TempClean", $task, 6, "", "", 3, $null)

 

I imported the TaskScheduler module and used the New-Task function to create a task.  I then set the task to run every day at 1:00pm.  The action the task performs is to first start PowerShell in the present working directory. Create a script block to run and encode it as shown.  The two references to $task show me the contents of the task variable.  its worth a look especially the XML.

Connect-TaskScheduler is used to create a variable I can use for scheduling the task.

We can see the scheduled task using

Get-ScheduledTask

We can run the task outside of the scheduled time

PS> Get-ScheduledTask -Name TempClean | Start-Task

Name          : TempClean
InstanceGuid  : {218CB104-5FBF-4611-B02D-151AA20363C1}
Path          : \TempClean
State         :
CurrentAction : C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe
EnginePID     : 1948

And we can remove the task

Get-ScheduledTask -Name TempClean | Remove-Task

the task scheduler API is documented on MSDN. http://msdn.microsoft.com/en-us/library/aa383614(VS.85).aspx

This holds a lot of possibilities including creating tasks on remote machines.  Needs more investigation especially why the functions aren’t working for me.

Posted by RichardSiddaway | with no comments

Extension for temporary files

When I did the post on creating temporary files http://msmvps.com/blogs/richardsiddaway/archive/2009/11/05/creating-temporary-files.aspx I said I’d modify it so the file would be created with a given extension.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
## asssume that want temp files in multiples of 1KB or empty
## renaming Get-ChildItem -Path c:\test\test2 | Rename-Item -NewName {$_.Name -replace "\....", ".txt"}
function New-TempFile {
param (
[string]$path = "C:\Test",
[int]$size = 1KB,
        [string]$ext = ""
)
if (!(Test-Path -Path $path)){throw "Invalid path"}
$file = [System.IO.Path]::GetRandomFileName()
    if ($ext -ne "") {
        if ($ext.StartsWith(".")){$ext = $ext.Remove(0,1)}
        $file = $file -replace "\....", ".$ext"
    }

if ($size -eq 0){
New-Item -Path $path -Name $file -ItemType 'File'
}
else {
$data = "*" * 128
$lines = [System.Math]::Ceiling(($size / 1kb) * 8)
for ($i=1; $i -le $lines; $i++){
Add-Content -Path $(Join-Path -Path $path -ChildPath $file) -Value $data
}
        Get-ChildItem -Path $path -Filter $file
}
}

 

It just involves adding another parameter $ext with a default empty string value.  If we give an extension

New-TempFile -size 2kb -ext "txt" -path "c:\test\test2"

Then we test to see if it starts with a .   and remove it.  We then replace the extension of the randomly generated filename using a regular expression match on the extension [ the regex is translated as 3 characters following a .  The \ is an escape character so . is read literally rather than as a wildcard]

Everything else is the same.  One other thing I might do is change the way data is put into the files.  For larger files its slow so I might do something different there.  This is one of great features of working with PowerShell like this – I can get a working script quickly and then refine it over time as I get chance to think about it.  Because I can work interactively at the prompt I can quickly test ideas before changing the script.

Posted by RichardSiddaway | with no comments

Security Essentials

 

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
#Requires -version 2.0
## wrapper functions for Microsoft Security Essentials
$mse = "C:\Program Files\Microsoft Security Essentials\MpCmdRun.exe"

function Start-Scan{
    param ([int]$type = 1)
    switch ($type){
    0   {   Write-Host "Starting Scan based on configuration"
            & $mse -scan -scantype 0
            break 
        }   
    1   {   Write-Host "Starting Quick Scan"
            & $mse -scan -scantype 1
            break 
        }
    2   {   Write-Host "Starting Full Scan"
            & $mse -scan -scantype 2
            break 
        }
   
   
    }
}

 

During my enforced break from posting in September & October PowerShell v2 was released for down level systems including Vista, Windows 2008, Windows 2003 and XP.  I’ve set up an XP machine to try it on and needed some AV. Not having licenses left for AVG which is my current AV product I decided to try Microsoft Security Essentials as well (its free which is always a good point for test machines)

Security Essentials comes with a command line tool – but its not PowerShell. I can get two wins here by creating a module that wraps the mpcmdrun tool in PowerShell. It is also a good example of how to run a legacy command line tool within PowerShell and pass parameters to it.

This function is the start – need to perform a scan – its easier to remember start-scan than the syntax of mpcmdrun.  I’ll can refine this by testing the range of the parameter and adding more functions.

Posted by RichardSiddaway | with no comments
Filed under:

Getting Change Events

I wasn’t particularly happy with the script for getting change events on the filesystemwatcher we discussed last time.  As a quick recap we ended up with this

001
002
003
004
Get-Event -SourceIdentifier "File System Changed" | where {($_.EventIdentifier % 2) -eq 1} | foreach {
    "{0}, {1}, {2}" -f   $_.SourceIdentifier, $_.SourceEventArgs.FullPath, $_.TimeGenerated
   
}

 

Which depends on the correct identification of the order in which events are issued.  That seemed like too much manual intervention. It was too late last night to solve so I had another look today and came up with this

001
002
Get-Event -SourceIdentifier "File System Changed" | Group TimeGenerated | where {$_.Count-eq 2} | 
foreach {$time = $_.Name; Get-Event | where {$_.TimeGenerated.ToString() -eq $time}| select -First 1}

 

use get-event with the correct source identifier. We then group on timegenerated.  File Changes will generate two change records per event so we select where the count is 2.  Pass those into a foreach and retrieve the events matching that time. We select the first one of each pair to only access a single record. One neat line of PowerShell does it all.

Posted by RichardSiddaway | with no comments

Watching the file system

We saw how to watch for WMI events http://msmvps.com/blogs/richardsiddaway/archive/2009/11/07/powershell-wmi-events.aspx. In this post we will look at watching the file system. This time we will use the .NET System.IO.FileSystemWatcher object which means we use Register-ObjectEvent instead of Register-WmiEvent.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
## folder to watch
$folder = "C:\test"
## watch all files
$filter = "*"
## events to watch
$events = @("Changed", "Created", "Deleted", "Renamed")
##
## create watcher
$fsw = New-Object -TypeName System.IO.FileSystemWatcher -ArgumentList $folder, $filter
$fsw.IncludeSubDirectories = $true
##
## register events
foreach ($event in $events){
    Register-ObjectEvent -InputObject $fsw -EventName $event -SourceIdentifier "File System $event"
}

 

We can start by defining the folder we want to watch and which files.  In this case I want to watch all files. We can restrict it to certain files e.g. $filter = “*.txt”

Wildcards work in the normal way for this filter.  We could even restrict to a single file. The events we are interested in are defined.

After creating a System.IO.FileSystemWatcher object using the folder and filter in the construction we set the IncludeSubDirectories property so we are watching the whole path.

Finally we need to register an event for each event we are interested in. Notice how the SourceIdentifier changes to identify the particular event.

We can see the Eventsubscribers we have created.

PS> Get-EventSubscriber | Select SubscriptionId, EventName, SourceIdentifier | ft -a

SubscriptionId EventName SourceIdentifier
-------------- --------- ----------------
             3 Changed   File System Changed
             4 Created   File System Created
             5 Deleted   File System Deleted
             6 Renamed   File System Renamed

We now need to perform some actions on the files – create, change, rename, delete that we are monitoring

PS> Get-Event | group SourceIdentifier

Count Name                      Group
----- ----                      -----
    7 File System Changed       {System.Management.Automation.PSEventArgs, System.Management.Automation.PSEventArgs,...
    1 File System Renamed       {System.Management.Automation.PSEventArgs}
    1 File System Created       {System.Management.Automation.PSEventArgs}
    1 File System Deleted       {System.Management.Automation.PSEventArgs}

If we look at this in more detail

PS> Get-Event | select EventIdentifier, SourceIdentifier, TimeGenerated

                        EventIdentifier SourceIdentifier                        TimeGenerated
                        --------------- ----------------                        -------------
                                      1 File System Changed                     08/11/2009 11:46:39
                                      2 File System Changed                     08/11/2009 11:46:39
                                      3 File System Renamed                     08/11/2009 11:49:53
                                      4 File System Changed                     08/11/2009 11:49:53
                                      5 File System Created                     08/11/2009 11:53:18
                                      6 File System Changed                     08/11/2009 11:53:18
                                      7 File System Changed                     08/11/2009 11:53:31
                                      8 File System Changed                     08/11/2009 11:53:31
                                      9 File System Deleted                     08/11/2009 11:54:22
                                     10 File System Changed                     08/11/2009 11:54:22

 

Note that we get pairs of events – the second event of the pair is always a change event.  If we just want to see the changes (and remove duplicates). Just be careful on this and check whether its the odd or even record you need if running multiple registered events. 

001
002
003
004
Get-Event -SourceIdentifier "File System Changed" | where {($_.EventIdentifier % 2) -eq 1} | foreach {
    "{0}, {1}, {2}" -f   $_.SourceIdentifier, $_.SourceEventArgs.FullPath, $_.TimeGenerated
   
}

 

We can use Get-Event to pull the change events from the queue. The modulo arithmetic on the EventIdentifier ensures that we only get the first change record. We can then dump the file path and time of change.

In a similar way we can interrogate the other events

001
002
003
004
Get-Event -SourceIdentifier "File System Created" | foreach {
    "{0}, {1}, {2}" -f   $_.SourceIdentifier, $_.SourceEventArgs.FullPath, $_.TimeGenerated
   
}

 

001
002
003
004
Get-Event -SourceIdentifier "File System Renamed" | foreach {
    "{0}, {1}, {2}, {3}" -f   $_.SourceIdentifier, $_.SourceEventArgs.OldFullPath, $_.SourceEventArgs.FullPath, $_.TimeGenerated
   
}

 

001
002
003
004
Get-Event -SourceIdentifier "File System Deleted" | foreach {
    "{0}, {1}, {2}" -f   $_.SourceIdentifier, $_.SourceEventArgs.FullPath, $_.TimeGenerated
   
}

 

These four could be combined into a single script if required.

Combined with the process tracing we did previously we can now track what programs are started on a system and what files are accessed.

Technorati Tags: ,,
Posted by RichardSiddaway | with no comments

Multiple test files

Back here http://msmvps.com/blogs/richardsiddaway/archive/2009/11/05/creating-temporary-files.aspx or http://richardsiddaway.spaces.live.com/blog/cns!43CFA46A74CF3E96!2595.entry I showed how to create temporary test files.

We can simply create multiple test files like this

1..10 | foreach {new-tempfile -path c:\test\test1 -size $(Get-Random -Maximum 1mb -Minimum 1kb)}

A one line PowerShell script that generates 10 files in the given folder with random names and sizes.

Technorati Tags: ,,
Posted by RichardSiddaway | with no comments

PowerShell WMI events

In my previous post ( http://richardsiddaway.spaces.live.com/blog/cns!43CFA46A74CF3E96!2598.entry  or http://richardsiddaway.spaces.live.com/blog/cns!43CFA46A74CF3E96!2598.entry ) I started to look at WMI events in PowerShell v2.  The win32_process class was used but all that showed us was that a process had started. We need a bit more information.  A bit of digging brought up the Win32_ProcessStartTrace class that seems to do what we want. 

Register-WMIEvent allows us to specify the class we want to use rather than a query – however if we try that we don’t get anything returned  - oops.

Looking through the help for Register-WMIEvent shows that we have the possibility of performing an action when the event occurs.  The action scriptblock can use a number of variables including $Event, $EventSubscriber, $Sender, $SourceEventArgs, and $SourceArgs automatic variables.  Wanting to understand these variables I tried dumping it though get-member. 

PS> Register-WmiEvent -Query "Select * FROM Win32_ProcessStartTrace" -Action {$Event | gm}

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
2               c1016218-f80... NotStarted False                                $Event | gm

The subscription runs as a PowerShell job. Using the opening of Notepad to trigger the event we can see that data is returned.

PS> Get-Job

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
2               c1016218-f80... Running    True                                 $Event | gm

And see that we have a few properties to play with.  ComputerName may come in useful if we are dealing with remote machines.

PS> Receive-Job -Id 2

   TypeName: System.Management.Automation.PSEventArgs

Name             MemberType Definition
----             ---------- ----------
Equals           Method     bool Equals(System.Object obj)
GetHashCode      Method     int GetHashCode()
GetType          Method     type GetType()
ToString         Method     string ToString()
ComputerName     Property   System.String ComputerName {get;}
EventIdentifier  Property   System.Int32 EventIdentifier {get;}
MessageData      Property   System.Management.Automation.PSObject MessageData {get;}
RunspaceId       Property   System.Guid RunspaceId {get;}
Sender           Property   System.Object Sender {get;}
SourceArgs       Property   System.Object[] SourceArgs {get;}
SourceEventArgs  Property   System.EventArgs SourceEventArgs {get;}
SourceIdentifier Property   System.String SourceIdentifier {get;}
TimeGenerated    Property   System.DateTime TimeGenerated {get;}

The properties look similar to those we saw in the last post.  Lets dig into SourceEventArgs

PS> Register-WmiEvent -Query "Select * FROM Win32_ProcessStartTrace" -Action {$Event.SourceEventArgs | gm}

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
3               8d4246a5-5f8... NotStarted False                                $Event.SourceEventArgs...

PS> Get-Job

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
3               8d4246a5-5f8... Running    True                                 $Event.SourceEventArgs...

PS> Receive-Job -Id 3

   TypeName: System.Management.EventArrivedEventArgs

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString()
Context     Property   System.Object Context {get;}
NewEvent    Property   System.Management.ManagementBaseObject NewEvent {get;}

 

Only thing here that look interesting is NewEvent

PS> Register-WmiEvent -Query "Select * FROM Win32_ProcessStartTrace" -Action {$Event.SourceEventArgs.NewEvent | gm}

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
4               0857a744-1d3... NotStarted False                                $Event.SourceEventArgs...

PS> Receive-Job -Id 4

   TypeName: System.Management.ManagementBaseObject#\Win32_ProcessStartTrace

Name                MemberType Definition
----                ---------- ----------
ParentProcessID     Property   System.UInt32 ParentProcessID {get;set;}
ProcessID           Property   System.UInt32 ProcessID {get;set;}
ProcessName         Property   System.String ProcessName {get;set;}
SECURITY_DESCRIPTOR Property   System.Byte[] SECURITY_DESCRIPTOR {get;set;}
SessionID           Property   System.UInt32 SessionID {get;set;}
Sid                 Property   System.Byte[] Sid {get;set;}
TIME_CREATED        Property   System.UInt64 TIME_CREATED {get;set;}
__CLASS             Property   System.String __CLASS {get;set;}
__DERIVATION        Property   System.String[] __DERIVATION {get;set;}
__DYNASTY           Property   System.String __DYNASTY {get;set;}
__GENUS             Property   System.Int32 __GENUS {get;set;}
__NAMESPACE         Property   System.String __NAMESPACE {get;set;}
__PATH              Property   System.String __PATH {get;set;}
__PROPERTY_COUNT    Property   System.Int32 __PROPERTY_COUNT {get;set;}
__RELPATH           Property   System.String __RELPATH {get;set;}
__SERVER            Property   System.String __SERVER {get;set;}
__SUPERCLASS        Property   System.String __SUPERCLASS {get;set;}

 

Now we have got to the information we need.  So how can we use this.  Up to now we have just allowed the job to run and then picked the data from the job.  One option is to write the data to the prompt as shown in this example  http://blogs.msdn.com/powershell/archive/2009/08/30/exploring-wmi-with-powershell-v2.aspx.  A lot of this digging was because I didn’t understand how this was put together.  PowerShell really is the best way to discover how to use PowerShell!!

This gets us to this script which is modified from the PowerShell Team blog

 

001
002
003
004
005
006
007
008
009
010
011
## query
$q = "Select * from Win32_ProcessStartTrace"
## action script block
$a = {
    $eSEANE = $Event.SourceEventArgs.NewEvent
    $str = 'Computer {0},ID {1}, Name "{2}", Time {3}, Source {4}' 
    $data = $str -f $Event.Sender.Scope.Path.Server, $eSEANE.ProcessId, `
      $eSEANE.ProcessName, $Event.TimeGenerated, $Event.SourceIdentifier
    Write-Host $data
}
Register-WmiEvent -Query $q -SourceIdentifier "Process Start" -Action $a

 

Turns out the ComputerName parameter doesn’t work but a comment on the blog shows how Jeffrey Hicks solved the problem.

What we get now is a listing at our PowerShell prompt  when a new process starts. We can keep working and the data comes through when the prompt is idle.

Next we will look at closing a process and recording the data in a log

Technorati Tags: ,,
More Posts Next page »