Friday Fun: Get Day of the Year with PowerShell

calendarEarlier this week I was having some fun with @EnergizedTech on Twitter, playing around with dates in PowerShell. I’m not even sure where we started but the experience got me thinking and it’s Friday so let’s have some fun.

While I can easily find out what the day of the year is for a given date:

there’s no method to convert an integer to a day of the year. But it really isn’t that hard. Here’s a simple function.

The stuff around the command is longer than the command itself. But I don’t mind. The function takes an integer value between 1 and 366, the most possible days in a year. You can also specify a year, but it defaults to the current year. The function then loops using a While loop which keeps adding one day to until the day of the year matches the target day of the year. I used a While loop because it is possible that the expression will be True immediately and there’s no need to process the scriptblock. Copy and paste the code and try for yourself!

UPDATE: I knew as soon as I posted that someone would show me a better way. And they did. I was clearly overthinking the problem. I’ve updated the code to show the smart way to do this. And now the solution is so short, you probably don’t even need the function.

Variable Validation

In PowerShell v3 there is a new feature you might not be aware of that could save you pain and headaches. This is something you could use in scripting as well as the console. In fact, I think using it in the console is an especially smart idea.

In PowerShell v2 scripts and functions we had the ability to add validation tags to parameters. These tags could perform different validation tests on a parameter value. If the value failed, the script or function would throw an exception. I’d rather have the command fail to start than to fail halfway through. Now in PowerShell v3 we can use these same tags on variables in our scripts and even the console. I’ve written about a number of these validation tags in the past on my blog. Here’s an example of what we can do in v3.


PS C:\> [validateRange(1,10)]$i=5

In this example I’ve added a validation test to verify that any value for $i is between 1 and 10. I can use the variable as I normally would. Even change the value.


PS C:\> $i*2
10
PS C:\> $i+=1
PS C:\> $i*2
12

But watch what happens when I try to use a value outside of the accepted range:


PS C:\> $i=30
The variable cannot be validated because the value 30 is not a valid value for the i variable.
At line:1 char:1
+ $i=30
+ ~~~~~
+ CategoryInfo : MetadataError: (:) [], ValidationMetadataException
+ FullyQualifiedErrorId : ValidateSetFailure

I get an exception. This is much better than setting a value that will cause another error later. The sooner you can detect potential problems the better.

Perhaps this example isn’t compelling. But there are other validation tests you might want to take advantage of. Maybe a regular expression pattern.


PS C:\> [validatepattern({\\\\\w+\\\w+})]$unc="\\server01\share"

If I later decide to change the value, the validation will help me catch typos.


PS C:\> $unc="\server02\data"
The variable cannot be validated because the value \server02\data is not a valid value for the unc variable.
At line:1 char:1
+ $unc="\server02\data"
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ValidationMetadataException
+ FullyQualifiedErrorId : ValidateSetFailure

The bottom line is that if your variable values will change, using a validation tag will ensure new values will work. If you don’t want to sift through the blog learning more about the validation tags, take a look at my Scripting Help module.

PowerShell Scripting with [ValidateNotNullorEmpty]

I’ve been writing about the different parameter validation attributes that you can use in your PowerShell scripting. One that I use in practically every script is [ValidateNotNullorEmpty()]. This validation will ensure that something is passed as a parameter value. I’m not talking about making a parameter mandatory; only that if the user decides to use the parameter that something is passed. Let’s look at my demo.


#requires -version 2.0

Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a process name like lsass")]
[ValidateNotNullorEmpty()]
[string]$Name,
[Parameter(Position=1)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:computername

)

Try {
Get-Process -Name $name -ComputerName $computername -errorAction "Stop" | Select *,
@{Name="Runtime";Expression={(Get-Date)-$_.StartTime}}
}

Catch {
Write-Warning $_.Exception.Message
}

I’ve used the attribute on both parameters. The first parameter I’ve also made mandatory which makes it more likely that something will be entered. But if not, then the script will fail.

I would get a similar message if the user forgot to complete the command.

Even though I’m using a default value for Computername, as soon as the parameter is detected PowerShell assumes I’m going to use a different value.

Validation should work for missing values, a variable that might be null, or in general anything that is meaningless. However, it won’t prevent something quirky like this:


PS S:\> $p=" "
PS S:\> .\demo-ValidateNotNull.ps1 -name $p
WARNING: Cannot find a process with the name " ". Verify the process name and
call the cmdlet again.

The variable, while semantically meaningless to us, is not null or empty but a string of 1 space. If there’s the chance you might run into this situation then you can add additional validation checks to the parameter.

If you’d like, feel free to download Demo-ValidateNotNull and see for yourself.

PowerShell Scripting with [ValidateCount]

Here’s another parameter validation attribute you might want to use in your PowerShell scripting and functions. If your parameter can take an array of values, you might want to limit that array to a certain size. For example, your parameter can take an array of computer names but you don’t want to process more than 5 for some reason. This is where [ValidateCount()] comes in to play.

This attribute takes two values, the minimum number of accepted parameter values and the maximum.


[ValidateCount(1,10)]
[string[]]$Computername

If used, this would mean I would need at least one computername but no more than 10. You could also set both values the same if you wanted an exact number:


[ValidateCount(2,2)]
[int[]]$Numbers

Now, I’d have to pass exactly 2 numbers as parameter values. Let’s look at a more complete example.


#requires -version 2.0

Param (
[Parameter(Position=0,Mandatory=$True)]
[ValidateCount(1,5)]
[string[]]$Name
)

Foreach ($item in $name) {

#display the name in a random color
Write-Host $item -ForegroundColor ([system.consoleColor]::GetValues("system.consolecolor") | get-random)

}

This simple script writes each name in a random color, assuming I pass no more than 5 names.

If I exceed that count, PowerShell will throw a tantrum (I mean exception).

When you use this validation test, be sure your parameter is set to accept an array of values, e.g. [string[]]. If you’d like to try out my sample code feel free to download Demo-ValidateCount.

PowerShell Scripting with [ValidateLength]

In continuing the exploration of parameter validation attributes, today we’ll look at [ValidateLength()]. You can use this attribute in your PowerShell scripting to validate that a parameter value is at least a certain length and no more and a certain length. In other words, it has to be just right. Here’s what it looks like:


[ValidateLength(2,10)]
[string]$Fooby

In this example any value for -Fooby must be at least 2 characters long and no more than 10. Anything outside of that range and PowerShell will raise an exception. You have to supply both values in the validation attribute. An alternative would be to use [ValidateScript()].


[ValidateScript({ $_.Length -ge 5})]
[string]$Name

Now, any value for -Name must be at least 5 characters and there is no upper limit. Here’s a more complete example.


#requires -version 2.0

[cmdletbinding(SupportsShouldProcess=$True)]

Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter name between 5 and 15 characters")]
[ValidateLength(5,15)]
[string]$Name,
[Parameter(Position=1,Mandatory=$True,HelpMessage="Enter password between 7 and 64 characters")]
[ValidateLength(7,64)]
[ValidatePattern({^\S+$})]
[string]$Password,
[string]$Computername=$env:computername,
[switch]$Passthru
)

Write-Host "Creating $name with password of $Password on $computername" -ForegroundColor Green
[ADSI]$Server="WinNT://$computername"
$User=$server.Create("User",$Name)

if ($pscmdlet.ShouldProcess($User.Path)) {
Write-Host "Committing new account changes" -ForegroundColor Green

<# #uncomment the next lines if you really, really want to do this $User.SetInfo() Write-Host "Setting password" -ForegroundColor Green $User.SetPassword($Password) If ($passthru) { Write-Output $User } #>
}

This script will create a local user account. I’m asking that the user name be between 5 and 15 characters and that the password be between 7 and 64 characters. I’ve also added a second validation check on the password using [ValidatePattern()] to verify it doesn’t contain any spaces.

The exception message you see with [ValidateLength()] depends on where you fall short.


PS S:\> .\Demo-ValidateLength.ps1 Jeff Password123
C:\scripts\Demo-ValidateLength.ps1 : Cannot validate argument on parameter 'Nam
e'. The number of characters (4) in the argument is too small. Specify an argum
ent whose length is greater than or equal to "5" and then try the command again
.
At line:1 char:26
+ .\Demo-ValidateLength.ps1 <<<< Jeff Password123 + CategoryInfo : InvalidData: (:) [Demo-ValidateLength.ps1], Para meterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateLe ngth.ps1 PS S:\> .\Demo-ValidateLength.ps1 JeffJeffJeffJeff Password123
C:\scripts\Demo-ValidateLength.ps1 : Cannot validate argument on parameter 'Nam
e'. The argument length of 16 is too long. Shorten the length of the argument t
o less than or equal to "15" and then try the command again.
At line:1 char:26
+ .\Demo-ValidateLength.ps1 <<<< JeffJeffJeffJeff Password123 + CategoryInfo : InvalidData: (:) [Demo-ValidateLength.ps1], Para meterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateLe ngth.ps1

If you'd like, you can download Demo-ValidateLength and try it out for yourself.