Expert Solution for 2011 Scripting Games Advanced Event 9: Use a PowerShell Function to Simplify Logging in Your Scripts

Expert Solution for 2011 Scripting Games Advanced Event 9: Use a PowerShell Function to Simplify Logging in Your Scripts

  • Comments 1
  • Likes

Summary: Microsoft PowerShell MVP, Arnaud Pettijean, shows how to create a function to simplify script logging while solving 2011 Scripting Games Advanced Event 9.

Microsoft Scripting Guy, Ed Wilson, here. Arnaud Petitjean is our expert commentator for Advanced Event 9.

Photo of Arnaud Pettijean

Hi ! Arnaud Petitjean online. I’m from France, and I’m the founder of the French Speaking Windows PowerShell Community. I am a Windows PowerShell MVP, and I am going to try to solve this advanced event.

Worked solution

I will solve Advanced Event 9 with several steps. The first step will be the most straightforward and aimed at obtaining the points. The next step will be explanations and tricks to make a reusable script to be used in a real admin’s life.

So here’s the code. Copy and paste this block at the end of a script:

$myDocuments = [environment]::GetFolderPath('myDocuments')

$logDir = 'HSGLogFiles'

 

# File name’s generation

$logName = (Get-Date -Format 'yyyyMMdd') + '_' + $env:username + '.log'

 

# Testing the existence of the directory containing the log file

if (-not (Test-Path "$myDocuments\$logDir") )

{

    New-Item -Path $env:userprofile\Documents -Name HSGLogFiles -ItemType Directory | Out-Null

}

 

# Testing the existence of the log file

if ( !(Test-Path "$myDocuments\$logDir\$logName") )

{

    "* All the text here will be written in the log file *" > $myDocuments\$logDir\$logName

}

The script begins by initializing some variables. The first one contains the path to the My Documents folder. As you can see, I use a static method from the .NET Framework 2.0 to get the path of the My Documents folder. This technique is very useful because the Environment class give you access to the path of all special folders such as Application Data, Desktop, My Pictures, My Music, SentTo, and Startup. The Environment class can even give you more—for instance, the operating system version, the number of physical processors, and the uptime of the system. Indeed, you have more information than with the classical Windows environment variables. Of course, we could have written $myDocuments = "$env:userprofile\Documents", and it would have been okay, but it is less reliable because the My Document folder may have been renamed or moved by the user (unlikely, though).

Then, I create the log file names by concatenating the current date formatted as expected with the user name (extracted from the $env:username variable) and with the extension ‘.log’.

If the directory HSGLogFiles does not exist, I create it. But if it exists, I do nothing. Notice here the use of the very practical cmdlet Test-Path, which returns True if a file or a directory exists and False in the other case. Notice also the use of Out-Null to redirect the output of New-Item according to the scenario of this event.

Finally, I test the existence of the log file on the disk. If it does not exists, it means that it is the first time that the script launched this day, so I create the file by passing text to the superior sign to send it to the log file. Otherwise, if the file exists, it means that the user has run the script multiple times on the same day, so nothing is done to preserve the original log file (which is created during the first run of the day).

All right. We now have entirely solved this event. But how can we make this script reusable to make it valuable enough to be included in our admin toolbox? Well, for that I could use a function, or even better, an advanced function. We are going to see that…

Before I start scripting, I try to imagine how I would like the script to behave. I think it would be convenient to have a function or a command called Write-Log. For instance, and it would be great if it could work like this:

1.  Write-Log –Message ‘Place here the text to write to the log file’

In this case, the log file will be created in the default directory: %myDocuments%\HSGLogFiles

 

2.  Write-Log –Message ‘Blah blah blah’ –Path c:\log

In this case, the log file will be created in c:\log.

 

3.  ‘Blah blah blah’ | Write-Log

In this case, the log file will be created in the default directory: %myDocuments%\HSGLogFiles

 

4.  ‘Blah blah blah’ | Write-Log –Path c:\log

In this case, the log file will be created in c:\log.

This is possible thanks to Windows PowerShell 2.0 and the advanced functions. As you can see, our script is supposed to behave exactly as a native cmdlet. To do so, have a look at the following code:

Function Write-Log

{

    Param (

        [String] $Path = $(Join-Path -Path ([environment]::GetFolderPath('myDocuments')) `

                                     -ChildPath 'HSGLogFiles'),

        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]       

        [String] $Message

        )

 

    process

    {

        # File's name generation

        $logName = (Get-Date -Format 'yyyyMMdd') + '_' + $env:username + '.log'

 

        # Testing the existence of the directory containing the log file

        if ( -not (Test-Path $Path) )

        {

            New-Item -Path $Path -ItemType Directory | Out-Null

        }

 

        # Testing the existence of the log file, if it exists we do nothing

        if ( !(Test-Path "$Path\$logName") )

        {

            $Message > $Path\$logName

        }

    }

}

The first thing to notice is that I have created a function, and this function defines two parameters. The first one is a string named Path. As you see, I initialize this parameter with some code. This code will be executed only if the user does not use the Path parameter when using Write-Log. It’s kind of a default value for this parameter.

The second parameter is a string called Message. This one is a little bit “special” because just before naming it, I put: [Parameter(ValueFromPipeline = $true, Mandatory = $true)]. This mentions that this parameter is mandatory and it can accept its value from the pipeline. Thanks to this extra line of code, this enables points 3 and 4 on our wish list to work. To be perfectly clear, doing so turns our classical function to an advanced function.

The only counterpart when accepting value from the pipe is to put the rest of the code inside a process block. Doing so enables Windows PowerShell to process multiple objects from the pipeline. For example, we could call our advanced function like this:

PS > ‘Blah blah blah’, ‘foo’, ‘bar’ | Write-Log

Thanks to the process keyword, the process block would have been called three times because we have three objects to handle. Nevertheless, in our context, the context of this event, we do not want our script to behave like that because we do not want to overwrite our log file after the first run. Hence, I could omit the process keyword in the script.

Finally, now you can include the Write-Log function directly in all your scripts that need logging, and call it as shown in the wish list. Or you could create a module of your own and put all your valuable functions in this module. This latter choice enables you to easily distribute your module to any of your coworkers or to the Internet for the communities.

To create a module, you simply have to:

1. Create a Windows PowerShell script in which you place all your functions, and save it with the extension .psm1. For instance, you could call this script myModule.psm1.

2. Create a directory in one of the following locations:

a. user\documents\windowspowershell\modules\myModule

b. windows\system32\windowspowershell\V1.0\modules\myModule

3. Put the script that you created in step 1 in one of the previous locations.

Now to use your functions, you simply have to import the module like this:

Import-Module myModule

And that’s it! Following is my example with a module named ‘Log’.

Image of command output

Thank you Arnaud. I really appreciate the work you put into your solution.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment