Event 1: My way…

This year judges should focus on blogging. I’ve decided that it may be helpful to give attendees chance to return the favor – I will blog about things I like and dislike in your scripts. At the same time – I will post how I would do it. Feel free to complain, point out mistakes. I will try to post it as soon as entries are closed for submissions, so that I will do it “fresh” before I see any of your scripts – this way I won’t “steal” your ideas. Puszczam oczko

Beginner event looks relatively easy, but I see no information about existence of target folders, my assumption is that they exist, otherwise it gets far too complex for one liner. I would normally use aliases when writing one liner, but here I want to be as verbose as possible to make it easy to understand.

Get-ChildItem -Path C:\Application\Log\*\*.Log |             
    Where-Object {            
        $_.LastWriteTime -lt (Get-Date).AddDays(-90)            
    } | Move-Item -Destination {             
        "\\NASServer\Archives\{0}\" -f $_.Directory.Name             
    }            

I’ve used pretty advanced technique here – using script block for parameter that accepts value from pipeline by property name, to reflect folder structure in source folder on archived side. Obviously, I don’t expect beginners to do the same. But anyone who will – gets in my “favorite” bucket almost for certain.

Advanced event is not more than making tool out of this one liner. Tool relatively simple. Parameters should include path to source folder(s) (I thought that I would prefer tool that allows me to pass array of paths to logs), path to destination folder, and limit of days that log files age should exceed. I thought it may be useful to have also two more parameters: filter, that log files should match and force parameter. Seems obvious, that this tools should support WhatIf, Confirm, and having Force kind of make this implementation complete (so that even if ConfirmPreference is set to High – we still have an option to avoid prompting). Complete param block:

<#
    .Synopsis
        Normally I would include full help here...
#>            
            
[CmdletBinding(            
    SupportsShouldProcess = $true,            
    ConfirmImpact = 'Medium'            
)]            
param (            
    # Adding help for parameter here - you avoid issues with typos in .Parameter ParamName            
    [Parameter(            
        ValueFromPipelineByPropertyName = $true            
    )]            
    [Alias('Path','FullName')]            
    [ValidateScript({            
        if (Test-Path -PathType Container -Path $_) {            
            $true            
        } else {            
            throw "$_ is not valid directory."            
        }            
    })]            
    [string[]]$Source = @(            
        'C:\Application\Log\App1'            
        'C:\Application\Log\OtherApp'            
        'C:\Application\Log\ThisAppAlso'            
    ),            
            
    # And another parameter help...            
    [ValidateScript({            
        if (Test-Path -PathType Container -Path $_) {            
            $true            
        } else {            
            throw "$_ is not valid directory."            
        }            
    })]            
    [string]$Destination = '\\NASServer\Archives',            
            
    # Filtering files here...            
    [string]$Filter = '*.Log',            
            
    # Here we decided how old file should be.            
    [int]$Limit = 90,            
            
    # And here we force things.            
    [switch]$Force            
)            

In the begin block I configured two things: DateTime object that we will use for comparison for log files found, and parameters for Move-Item cmdlet that will be common for all moves performed:

$Max = (Get-Date).AddDays(-$Limit)            
    $MoveParams = @{            
        Confirm = $false            
        ErrorAction = 'Stop'            
    }            

In process block I perform necessary actions for each source folder: define target folder based on source’s leaf, make sure target folder exists, find all files that meet criteria (date-wise and filter-wise) and for each file found – perform move, with option to only display actions to be performed, or require confirmation for each file moved. All that with some verbose messages possible, if user will ask for them with Verbose parameter. Move can always go wrong, and we want to report it to the user – thus try/ catch with small report returned if something fails. If everything works fine – nothing is reported, as requested:

foreach ($Folder in $Source) {            
    $MoveParams.Destination = "$Destination\{0}" -f (Split-Path $Folder -Leaf)            
    try {            
        if (!(Test-Path -Path $MoveParams.Destination -PathType Container)) {            
            New-Item -ItemType Directory -Path $MoveParams.Destination -ErrorAction Stop            
        }            
        Write-Verbose "Processing folder: $Folder"            
        Get-ChildItem -Path $Folder -Filter $Filter |             
            Where-Object {            
                $_.LastWriteTime -lt $Max            
            } |            
            ForEach-Object {            
                if ($Force -or $PSCmdlet.ShouldProcess(            
                    $_.FullName,            
                    "Move to $($MoveParams.Destination)"            
                ))            
                {            
                    try {            
                        $File = $_.FullName            
                        Write-Verbose "Moving file: $File"            
                        Move-Item -Path $File @MoveParams            
                    } catch {            
                        "Problem moving file $File : $_"            
                    }            
                }            
            }            
        } catch {            
            "Problem with creating target folder: $_"            
        }            
}            

This is the way I would do it. But be sure: if you did it differently it’s still possible, that I will pick your script as my favorite one. Heck, this difference can be the reason for this decision. Puszczam oczko That’s what I love about Scripting Games: it doesn’t matter what is your “role”: attendee, spectator, judge – there is always chance to learn new things. And now – I move on to attendees entries. Can’t wait. Szeroki uśmiech

Advertisement

13 thoughts on “Event 1: My way…

  1. Pingback: Event 1: My way… | PowerShell.org

  2. I used [void][io.directory]::CreateDirecotry() in a Try/Catch to create my target directories.

    Test-Path will return $True if a file with that name already exists at the target path and then all your moves fail. Unlike New-Item, if the directory already exists the dotnet method doesn’t throw an error, it just happily continues on. It will throw a terminating error if the directory does not exist, and it cannot be created, and then the Catch block handles the notification.

    • Test-Path would return true if I would not use PathType parameter:
      test-path -PathType Container -Path C:\pagefile.sys
      False

      I must agree though that this piece of code is lousy, I’ve added it in last moment and haven’t really nailed it. Appreciate the tip with .NET method, makes perfect sense… 🙂

      • Ah. I missed that PathType.
        I like the dotnet method for this, because the way it behaves elimintates the need to do a test-path at all. It may have cost me “Crowd Source” points for using it, becuase if you don’t know it behaves that way it looks like you simply forgot to do the Test-Path.

  3. I did not realize that the parameter could be used in the “destination” part of the script. That’s really cool! In my case, I elected to create a simple script rather than one-liner. In practice, one liners are great for the person running the command. However, I’m struggling to get others to use PowerShell. I find that multiline scripts with very brief comments are much easier to digest by other new users.

    Also, I ended up adding the necessary code to create the folders on the destination side. This was really more about my convenience in testing the script. I didn’t want to manually create all these folders. 🙂

    • I actually started with creating code to build test environment, both folder and log files… so I didn’t care so much about folder existence. Penalty: not so smart test in advance category, as mjolinor pointed out. 😉

  4. Hey, Bartek – explain how you DECLARED support for -WhatIf and -Confirm… and how that support actually got IMPLEMENTED in your code! Big difference, and I want to make sure folks see it!

    • Fair point, Don. 🙂 I guess this may be worth another blog post though, or I will simply include it when I will discuss scripts that I like (or don’t) in advanced event 1. In either case – I think you right, this is something worth explanation.

  5. So I would have taken a point for using ! instead of -not but maybe that’s just my anti alias bias… Regardless, the only thing I hate about this script is that it is significantly better than my entry. Also I really like write-debug but nobody seems to use it.

    • Agree that -not is more obvious than ! – not sure if I would call alias though. Re: write-debug. I like to use it too, just couldn’t see any value for it here. I tend to use it in places where I may need to ‘Inquiry’, something that foo -Debug will cause when used with advanced function…

  6. Pingback: The 2013 Scripting Games, Beginner Event #1 | Mello's IT Musings

  7. Pingback: Advanced Event 1 – 2013 Powershell Scripting Games | Powershell Reflections

  8. Pingback: Event #1: Moving Old Files | PowerShell.org

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s