Summary: Microsoft Scripting Guy, Ed Wilson, shows how to use dynamic parameters to populate a list of printer names.
Microsoft Scripting Guy, Ed Wilson, is here. Using a Windows PowerShell function to send a test page to a printer is pretty cool (see Use PowerShell to Send Test Page to a Printer). But it would be even better if I did not have to type the name of the printer. I know I can use the Get-Printer function in Windows 8.1 to list all of my printers (and I can use a CIM session to do this remotely), so why can’t I run that script to create my list of available printers? It turns out that I can do this by using dynamic parameters. The problem is that it is a bit confusing to do.
Note I am not going to be able to explain all of this to everyone’s fullest satisfaction. It is a bit complicated. But hopefully, I will explain it well enough that you can copy my script and modify it to do other things. So in effect, what I hope to create is a dynamic parameter template that you can use to create powerful and useful scripts. This is cool stuff, and for me, it is worth the effort. Also note that you do not have to do this. You can easily use available tools that will populate a WinForm with a drop-down list of available printers. Part of the point of today’s post is to show you how you can do this. If you choose to do it is entirely up to you. Hope you enjoy the post as much as I had fun writing it.
When I use the Param statement to add parameters to a function, I am accepting input that will change the way the function works. Typically, I use parameters to permit me to change the target of a function, and I call the ComputerName parameter as shown here:
Function example
{
Param([string]$computername)
}
I can also set default values for a parameter, which gives me a default behavior for my function. Typically, I like to target a function on my local computer. I like to obtain this value from the environmental variables as shown here:
Function example
{
Param([string]$computername = $env:computername)
}
But what if I wanted to produce a list of available computers that might be on my target network? This would require running a script block, and unfortunately I cannot do this. I need a dynamic parameter.
Here is what I need to do to create a dynamic parameter:
- Use the DynamicParam keyword.
- Create a ParameterAttribute object.
- Create an attribute collection.
- Create a validation set.
- Create a RuntimeDefinedParameter.
- Create a RuntimeDefinedParameterDictionary object.
Create ParameterAttribute object
I need to create a ParameterAttribute object. To do this, I use the New-Object cmdlet, and then I create a ParameterAttribute object. This class resides in the System.Management.Automation namespace. (This is the namespace that contains all of the Windows PowerShell stuff). I store the newly created object in a variable named $attributes.
Now I want to specify a name for the parameter set. This is a straight-forward value assignment to the ParameterSetName property. I decide to call it __ParameterSets. I also decide to make the attribute mandatory. Here are the three lines of script that accomplish this task:
$attributes = new-object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $true
Create attribute collection
Now I need to create an attribute collection so I have a place to put the attributes that I just created. I use the New-Object cmdlet again, and this time, I store the returned attribute collection object in the $attributeCollection variable. The collection object comes from the System.Collections.ObjectModel namespace, and I specify that it will be of the [system.Attribute] type. When I have the object, I add my attributes to it by calling the Add method. This script is shown here:
$attributeCollection =
new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
Create validation set
I need to get my collection of printer names. I use the Get-CimInstance cmdlet to query the Win32_Printer WMI class, and I return only the Name property. I store these printer names in the $_Values variable.
Now I create another object. This time it is the ValidateSetAttribute object. Again, this comes from the System.Management.Automation .NET Framework namespace. When I am creating the ValidateSetAttribute class, I specify the printer names stored in the $_Values variable. I store the returned ValidateSetAttribute object in the $validateSet variable. I pass this object to the Add method of the AttributeCollection object that I created in the previous step. Here is the script that does this:
$_Values = (Get-CimInstance win32_Printer).name
$ValidateSet =
new-object System.Management.Automation.ValidateSetAttribute($_Values)
$attributeCollection.Add($ValidateSet)
Create RuntimeDefinedParameter
Whew…
I still have not created everything I need to permit me to create my dynamic parameter. I need to create two more objects, and finally I will be done. I now use the New-Object cmdlet to create a RuntimeDefinedParameter. When I do this, I need to tell it the name of the parameter. In this example, it is Printer.
I also need to tell it where to find the potential values. The values come from the $attributeCollection variable. I store the RunTimeDefinedParameter in the $dynParam1 variable. Here is the script that accomplishes this task:
$dynParam1 =
new-object -Type System.Management.Automation.RuntimeDefinedParameter(
"Printer", [string], $attributeCollection)
Create RuntimeDefinedParameterDictionary object
The last object I need to create is the RuntimeDefinedParameterDictionary. Once again, this dictionary object comes from the System.Management.Automation .NET Framework namespace. I add the name of Printer, and the RuntimeDefinedParameter object that I stored in the $dynParam1 variable.
Now it is time to return the RuntimeDefinedParameterDictionary object that I stored in the $paramDictionary object, so I call the Return keyword and pass the $paramDictionary variable as shown here:
$paramDictionary =
new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("Printer", $dynParam1)
return $paramDictionary }
Dude, it is a regular advanced function
At this point, it is just a regular advanced function. I add a Begin and a Process section to the function. I am not doing anything special in the begin section, and the process section is where I place my script block. Following the script block, I have my End section. This is shown here:
begin {}
process {
$printer = $PSBoundParameters.printer
Invoke-CimMethod -MethodName printtestpage -InputObject ( Get-CimInstance win32_printer -Filter "name LIKE '$printer'") }
end {}
}
When I run the function, I am presented with a nice drop-down list that permits me to select a printer. This is the dynamic portion of the parameters is shown in the following image:
To me, it is worth a little bit of extra script to enable this functionality. Here is the complete script:
Function out-TestPage {
[CmdletBinding()]
Param()
DynamicParam {
$attributes = new-object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $true
$attributeCollection =
new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$_Values = (Get-CimInstance win32_Printer).name
$ValidateSet =
new-object System.Management.Automation.ValidateSetAttribute($_Values)
$attributeCollection.Add($ValidateSet)
$dynParam1 =
new-object -Type System.Management.Automation.RuntimeDefinedParameter(
"Printer", [string], $attributeCollection)
$paramDictionary =
new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("Printer", $dynParam1)
return $paramDictionary }
begin {}
process {
$printer = $PSBoundParameters.printer
Invoke-CimMethod -MethodName printtestpage -InputObject ( Get-CimInstance win32_printer -Filter "name LIKE '$printer'") }
end {}
}
Join me tomorrow when we will have more cool Windows PowerShell stuff.
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