Categories

13496

Yes I’m sure

One of the great safety features of PowerShell are the –whatif and –confirm parameters on cmdlets. They can really be useful in preventing hideous, career limiting actions

 

We can create the same functionality on our advanced functions

function test-whatifconfirm {             
[CmdletBinding(SupportsShouldProcess=$true,             
    ConfirmImpact="High")]             
param (             
   [string]$computer="."             
)             
BEGIN{}#begin             
PROCESS{            
            
## Whatif code            
if ($psCmdlet.ShouldProcess("$computer", "get-process on")) {            
   get-process            
}            
            
}#process             
END{}#end            
            
}


put the cmdletbinding attribute into the function



 



[CmdletBinding(SupportsShouldProcess=$true,
    ConfirmImpact="High")]



 



if you want to use ConfirmImpact you need ShouldProcess=$true



 



We then need a bit of code to perform the tests



 



if ($psCmdlet.ShouldProcess("$computer", "get-process on")) {
   get-process
}



 



In this case if –whatif isn’t used get-process runs as this test shows



 



test-whatifconfirm                                                                                                                      



get-process runs



 



if we use –whatif then a message is printed – notice that the two parameters for ShouldProcess are in the order of object and message



 



test-whatifconfirm –WhatIf  



see message



What if: Performing operation "get-process on" on Target ".".



 



The ConfirmImpact attribute can be set to None, Low, Medium or High. Its usage depends on the value of $confirmpreference



The default for $confirmpreference is High



 



test-whatifconfirm –Confirm



will show the confirmation dialog



 



The ConfirmImpact attribute needs to be set equal to or higher than the value of $confirmpreference



 



Two great bits of functionality for minimal effort

Scripting Guy discusses PAM modules

My codeplex project publishing PowerShell Admin Modules (PAM) is discussed in this post

http://blogs.technet.com/b/heyscriptingguy/archive/2011/06/29/don-t-write-wmi-scripts-use-a-powershell-module.aspx

In particular the Get-OSInfo function from the PAMSysInfo module is heavily featured

Windows updates: 4 tidy up get-update

Looking at the get-update function we created earlier I wanted to tidy it up a bit.

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

function get-update {
[CmdletBinding()] 
param
 ( 
 
[switch]$hidden
 
) 

PROCESS{

$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()

# 0 = false & 1 = true
if ($hidden
){
 
$result = $searcher.Search("IsInstalled=0 and Type='Software' and ISHidden=1"
 )
}

else
 {
 
$result = $searcher.Search("IsInstalled=0 and Type='Software' and ISHidden=0"
 )
}


if ($result.Updates.Count -gt 0
){
 
$result.Updates |
 
 
select Title, IsHidden, IsDownloaded, IsMandatory,
 
 
IsUninstallable, RebootRequired, Description
}
else
 {
 
Write-Host " No updates available"
} 

}
#process

<# .SYNOPSIS Discovers available updates .DESCRIPTION Interrogates Windows updates for available software updates only. Optional parameter to display hidden updates .PARAMETER hidden A switch to display the hidden updates .EXAMPLE get-update Displays non-hidden updates .EXAMPLE get-update -hidden Displays hidden updates #>



}

Primary changes are:

  • to add a switch to the function to control the display of hidden updates
  • count the available updates and display message if there aren’t any
  • add more properties to the output- 7 properties defaults to a list so more readable as well
  • add comment based help

Advanced Function update

If you were around PowerShell back in November 2007 you might remember that the PowerShell v2 CTP was the new kid in town. One of really big things at the time was script cmdlets – that we now call Advanced Functions.

Back in this post

http://richardspowershellblog.wordpress.com/2007/11/19/powershell-v2-script-cmdlets/

I showed how to write an Advanced Function / Script cmdlet / thingy

Having stumbled over the post today I thought I better bring it up to date so rewrote the code as a PowerShell v2 advanced function

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

#Requires -Version 2
function write-myeventlog{ 
[CmdletBinding(SupportsShouldProcess=$true)] 
param ( 
[parameter(Position=0,
   Mandatory=$true,
   HelpMessage="Name of the event log to write to" )]
   [Alias("Name", "Log")]
 
  
[ValidateNotNullOrEmpty()]
   [string]$eventlogname,
 
  

[parameter(Position=1,
   HelpMessage="Message to write to event log" )]
   [Alias("msg")]
 
  
[ValidateNotNullOrEmpty()]
   [string]$Message,
 
   

[parameter(Position=2,
   HelpMessage="Type of event. Possible values are Error, Warning, Information, SuccessAudit, FailureAudit" )]
   [Alias("T")]
 
  
[ValidateNotNullOrEmpty()]
   [System.Diagnostics.EventLogEntryType]$Type

) 
BEGIN{}#begin
PROCESS{

$log = New-Object System.Diagnostics.EventLog
$log.set_log($EventLogName)
$log.set_source("PSscripts") 

if ($psCmdlet.ShouldProcess("$EventLogName", "Writing to"
)) {
   
$log.WriteEntry($Message,$Type
) 
}

}
#process
END{}#end
}

 

If you’ve looked at advanced functions this should be straight forward.

Define three parameters each of which is given a position. If the values are passed without the parameter name that is the order in which the data will be processed. Each parameter is given at least one alias and a help message. The value is also checked to make sure that it isn’t null or empty

The eventlog object is created and the message written. Because I used the [CmdletBinding()] attribute I can use –whatif as a parameter. The if statement allows the processing of -whatif

This code does exactly the same as the original but the syntax is up to date.

Not sure if the Write-Eventlog cmdlet was in the original CTP but don’t think it was which is why I used this example.

Advanced Function template

In this post I showed my Advanced Function template  http://msmvps.com/blogs/richardsiddaway/archive/2011/05/13/powershell-module-construction.aspx

I’ve since modified it to add the parameter validation methods. I can never remember them all so decided putting them in the template was the easiest way forward.

 

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

function aaaa-yyyyyy{ 
[CmdletBinding(SupportsShouldProcess=$true, 
    ConfirmImpact="Medium Low High None", 
    DefaultParameterSetName="XXXXX")] 
param ( 
[parameter(Position=0,
   Mandatory=$true,
   ParameterSetName="YYYYYYYYYY",
   ValueFromPipeline=$true, 
   ValueFromPipelineByPropertyName=$true,
   ValueFromRemainingArguments=$true,
   HelpMessage="Put your message here",
   Alias("CN", "ComputerName")  )] 
   [AllowNull()]
   [AllowEmptyString()]
   [AllowEmptyCollection()]
   [ValidateCount(1,10)]
   [ValidateLength(1,10)]
   [ValidatePattern("[A-Z]{2,8}[0-9][0-9]")]
   [ValidateRange(0,10)]
   [ValidateScript({$_ -ge (get-date)})]
   [ValidateSet("Low", "Average", "High")]
   [ValidateNotNull()]
   [ValidateNotNullOrEmpty()]
   [string]$computer="." 
) 
BEGIN{}#begin
PROCESS{

if ($psCmdlet.ShouldProcess("## object ##", "## message ##")) {
    ## action goes here
}

}#process
END{}
#end

<#
.SYNOPSIS
.DESCRIPTION
.PARAMETER <Parameter-Name>
.EXAMPLE
.INPUTS
.OUTPUTS
.NOTES
.LINK
#>

}

 

One or two may not be right on a string parameter but they are there as a reminder not code that will run

Advanced Functions: part 3

Lets start looking at validation of parameter values. We have a few options to try

  [ValidateCount(1,n)] 
  [ValidateLength(1,m)] 
  [ValidatePattern("[A-Z][A-Z]A-Z][0-9]")] 
  [ValidateRange(0,10)] 
  [ValidateScript({$_ -lt 4})] 
  [ValidateSet("red","blue","green")] 

  [ValidateNotNull()] 
  [ValidateNotNullOrEmpty()]

 

lets start with validating a range of values

001
002
003
004
005
006
007
008
function test1 {
    param (
        [int]
        [validateRange(1,5)]
        $p1  
)
    $p1 * 3   
}

We have a simple function that defines 1 parameter – in my recent post showing all of the possible parameters [datatype] referred to the data type we were assigning to the parameter in this case an integer.

We’ll define a valid range of 1-5 for this value

Our function does a simple multiplication to show its working.

As a test

PS> 1..5 | foreach {test1 $_}
3
6
9
12
15

Everything works.  what happens if we submit a value outside the allowed range?

PS> 4..7 | foreach {test1 $_}
12
15
test1 : Cannot validate argument on parameter 'p1'. The 6 argument is greater than the m
aximum allowed range of 5. Supply an argument that is less than 5 and then try the comma
nd again.
At line:1 char:22
+ 4..7 | foreach {test1 <<<<  $_}
    + CategoryInfo          : InvalidData: (:) [test1], ParameterBindingValidationExcep
   tion
    + FullyQualifiedErrorId : ParameterArgumentValidationError,test1
test1 : Cannot validate argument on parameter 'p1'. The 7 argument is greater than the m
aximum allowed range of 5. Supply an argument that is less than 5 and then try the comma
nd again.
At line:1 char:22
+ 4..7 | foreach {test1 <<<<  $_}
    + CategoryInfo          : InvalidData: (:) [test1], ParameterBindingValidationExcep
   tion
    + FullyQualifiedErrorId : ParameterArgumentValidationError,test1

 

4 and 5 are accepted but 6 and 7 are rejected.

If we fall outside the lower range

PS> 2..0 | foreach {test1 $_}
6
3
test1 : Cannot validate argument on parameter 'p1'. The 0 argument is less than the mini
mum allowed range of 1. Supply an argument that is greater than 1 and then try the comma
nd again.
At line:1 char:22
+ 2..0 | foreach {test1 <<<<  $_}
    + CategoryInfo          : InvalidData: (:) [test1], ParameterBindingValidationExcep
   tion
    + FullyQualifiedErrorId : ParameterArgumentValidationError,test1

A simple test that protects the function. Using this supposes we know what the range of values should be

Typo

Thanks to Shay for pointing out some typos in my last post.  The correct bits are

 

  [alias("alias_name")]
    [datatype]
    [AllowNull()]
    [AllowEmptyString()]
    [AllowEmptyCollection()]
    [ValidateCount(1,n)]
    [ValidateLength(1,m)]
    [ValidatePattern("[A-Z][A-Z]A-Z][0-9]")]
    [ValidateRange(0,10)]
    [ValidateScript({$_ -lt 4})]
    [ValidateSet("red","blue","green")]
    [ValidateNotNull()]
    [ValidateNotNullOrEmpty()]

 

 

Apologies for the error

Advanced Functions part 2

I thought it would be worth while looking at the range of parameters we can use with our functions.  We saw the mandatory parameter last time but that is just a starter.  If we examine the range of parameters we see that there are some to control how we receive the parameters and a big bunch that perform validation on the values.


#Requires -version 2.0
function fname {
[CmdletBinding(SupportsShouldProcess
=$true/$false,
                     ConfirmImpact
="None"|"Low"|"Medium"|"High",
                     DefaultParameterSetName
="set_name")]
param (
    [parameter(Mandatory
=$true,
        Position
=0,
        ParameterSetName=”set_name”,
        ValueFromPipeline
=$true,
        ValueFromPipelineByPropertyName
=$true,
        HelpMessage
="Your message goes here")] 
    [alias("alias_name")]
    [datatype]
    [AllowNull()]
    [AllowEmptyString()]
    [AllowEmptyCollection()]
    [ValidateCount(
1,n)]
    [ValidateLength(
1,m)]
    [ValidatePattern(
"[A-Z][A-Z]A-Z][0-9]")]
    [ValidateRange(
0,10)]
    [ValidateScript({
$_ -lt 4})]
    [ValidateSet(
"red","blue","green")]
    [ValidateNotNull()]
    [ValidateNotNullOrEmpty()] 
   $pname = default_value
)

    DynamicParam {PowerShell code}

  
Begin{}
  
Process{}
  
End{}

}


 


We wouldn’t actually use all of these at once on a single parameter – especially as some are mutually exclusive.


This list just covers the parameters – there are a bunch of methods we can use as well. We’ll look at those another time.


I’ll provide examples of using these in follow up posts


Advanced Functions part 1

Advanced functions are one of the major new features of PowerShell. They enable functions written in PowerShell to behave in the same way as cmdlets especially in the way they work on the pipeline.

However when you look at the help files

PS> get-help about*functions* | select Name

Name
----
about_functions
about_functions_advanced
about_functions_advanced_methods
about_functions_advanced_parameters
about_functions_cmdletbindingattribute

 

There is a lot of information to understand.  In this short series of posts I will explain how the various methods and parameters can be used to provide a lot of functionality for the absolute minimum of code.  As an example consider this simple function

 

001
002
003
004
005
006
007
function fone {
param (
    [int]$a,
    [int]$b
)   
    $a + $b
}

 

It takes two integers and returns the result.

As an aside I always put a type on the parameters, if at all possible, as it prevents errors that can occur by attempting to input the wrong data type

PS> fone 1 "a"
fone : Cannot process argument transformation on parameter 'b'. Cannot convert value "a" to type "System.Int32". Error: "Input string was not in a correct format."
At line:1 char:5
+ fone <<<<  1 "a"
    + CategoryInfo          : InvalidData: (:) [fone], ParameterBindin...mationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,fone

 

I always use put the parameters in a param block inside the function – its a consistency of style that helps me.

This function will work in PowerShell v1.

We can use the function

PS> fone 1 2
3

and everything works OK. But we can also perform this action

PS> fone 1
1

which may be OK in this example but in other functions could lead to problems.  In PowerShell v1 we would do something like this

001
002
003
004
005
006
007
008
009
010
011
012
function ftwo {
param (
    [int]$a,
    [int]$b
)   
    if (($a -eq 0) -or ($b -eq 0)) {
        Throw "both parameters must be given"
    }
    else {
        $a + $b
    }
}

 

PS> ftwo 1 2
3

PS> ftwo 1
both parameters must be given
At line:7 char:14
+         Throw <<<<  "both parameters must be given"
    + CategoryInfo          : OperationStopped: (both parameters must be given:String) [], RuntimeException
    + FullyQualifiedErrorId : both parameters must be given

BUT what do we do if we want one of the values to be zero!

In PowerShell v2 we can make the parameters mandatory ie you have to supply them.  All we have to do is add the statement [parameter(Mandatory=$true)] to our parameter definition.

001
002
003
004
005
006
007
008
009
010
function fthree {
param (
    [parameter(Mandatory=$true)]
    [int]$a,
   
    [parameter(Mandatory=$true)]
    [int]$b
)   
    $a + $b
}

 

PS> fthree 1 2
3

Supplying two parameters works fine.  If we only supply one parameter we are prompted for the other

PS> fthree 1

cmdlet fthree at command pipeline position 1
Supply values for the following parameters:
b: 2
3

and we can handle zeros

PS> fthree 1 0
1

One simple statement gives us a lot of power.

One thing to be aware of is that if you make a parameter mandatory any default values are ignored i.e. the mandatory check overrides the default.

Having seen an example of what we can do with advanced functions we’ll look at the whole structure of functions in the next post