What I want is to get-help to output the below for my cmdlet
SYNTAX
Get-Somehting -A <Object> [-Package <Object>] [<CommonParameters>]
Get-Somehting -A <Object> [-Names <string[]>] [<CommonParameters>]
Get-Somehting -B <Object> [-Package <Object>] [<CommonParameters>]
Get-Somehting -B <Object> [-Names <string[]>] [<CommonParameters>]
The following
Function Get-Somehting{
[CmdletBinding()]
Param(
[Parameter(Mandatory,
ParameterSetName='A')]
[System.Object]$A,
[Parameter(Mandatory,
ParameterSetName='B')]
[System.Object]$B,
[Parameter(Mandatory,
ParameterSetName='package')]
[System.Object]$Package,
[Parameter(Mandatory,
ParameterSetName='Names')]
[String[]]$Names)
Process{
}
}
gives me
SYNTAX
Get-Somehting -A <Object> [-Package <Object>] [-Names <string[]>] [<CommonParameters>]
Get-Somehting -B <Object> [-Package <Object>] [-Names <string[]>] [<CommonParameters>]
One of the most annoying and coolest features is parameter set names. What's cool is you can properly define your input for the 'path' you want your code to take, and rather check what 'path' was input instead of checking all the different input parameters. You want to be very careful when defining them, however, as pwsh REALLY wants to end up with just one invoked 'path', so you have to properly define your expected paths:
Function Get-Something {
[CmdletBinding()]
Param(
[Parameter(Mandatory,ParameterSetName = 'AName')]
[Parameter(Mandatory,ParameterSetName = 'APackage')]
[System.Object]$A,
[Parameter(Mandatory,ParameterSetName = 'BName')]
[Parameter(Mandatory,ParameterSetName = 'BPackage')]
[System.Object]$B,
[Parameter(Mandatory,ParameterSetName = 'AName')]
[Parameter(Mandatory,ParameterSetName = 'BName')]
[System.Object]$Package,
[Parameter(Mandatory,ParameterSetName = 'APackage')]
[Parameter(Mandatory,ParameterSetName = 'BPackage')]
[String[]]$Names)
Process {
$PSCmdlet.ParameterSetName
}
}
It's kind of messy, but it also makes sense as in your case you want to always have an "a or b" path and with that always define either names or packages, so you end up with 4 possible 'paths'.
Related
I have this cmdlet:
$dnstype = ,#("Primary","Secondary","Tertiary")
$dnsserver = ,#("192.168.1.11","192.168.1.12","192.168.150.13")
Set-HPEiLOIPv4NetworkSetting -connection $connection -DNSServer $dnsserver -DNSServerType $dnstype
This works fine.
However, I need to splat the parameters, because I am adding parameters depending on the known state of the object (this is because some parameters result in a reset which I'm trying to avoid if possible).
When doing the following the DNS servers are not amended as they need to remain "unrolled" when sent to the cmdlet.
$dnstype = ,#("Primary","Secondary","Tertiary")
$dnsserver = ,#("192.168.1.11","192.168.1.12","192.168.150.13")
$NewDCHPv4Settings = #{
Connection = $connection
InterfaceType = "Dedicated"
OutVariable = "IPv4Set"
OutputType = "Object"
DNSName = $ShortName.ToLower()
ErrorAction = "Stop"
Verbose = $true
}
# Check DNS and amend if required
if ($IPv4Settings.DNSServer -ne $dnsserver) {
Write-Host "DNS server entries not correct, amending"
$NewDCHPv4Settings.Add("DNSServer", $dnsserver)
$NewDCHPv4Settings.Add("DNSServerType", $dnstype)
}
Set-HPEiLOIPv4NetworkSetting #NewDCHPv4Settings
So the question - how do I add the unary arrays to the splat array so that the integrity is maintained and they process correctly?
As requested - here is the defintion:
Name : Set-HPEiLOIPv4NetworkSetting
ModuleName : HPEiLOCmdlets
Module : #{Name=HPEiLOCmdlets}
CommandType : Cmdlet
Definition :
Set-HPEiLOIPv4NetworkSetting [-Connection] <Connection[]> -InterfaceType <string[]> [-SharedNetworkPortType <string[]>] [-SNPPort <int[]>]
[-VLANEnabled <string[]>] [-VLANID <int[]>] [-DNSName <string[]>] [-DomainName <string[]>] [-NICEnabled <string[]>] [-FullDuplex <string[]>]
[-LinkSpeedMbps <string[]>] [-DHCPEnabled <string[]>] [-DHCPv4Gateway <string[]>] [-DHCPv4DomainName <string[]>] [-DHCPv4DNSServer <string[]>]
[-DHCPv4WINSServer <string[]>] [-DHCPv4StaticRoute <string[]>] [-DHCPv4NTPServer <string[]>] [-IPv4Address <string[]>] [-IPv4SubnetMask <string[]>]
[-IPv4Gateway <string[]>] [-IPv4StaticRouteIndex <int[][]>] [-IPv4StaticRouteDestination <string[][]>] [-IPv4StaticRouteMask <string[][]>]
[-IPv4StaticRouteGateway <string[][]>] [-DNSServerType <string[][]>] [-DNSServer <string[][]>] [-RegisterDDNSServer <string[]>] [-PingGateway
<string[]>] [-RegisterWINSServer <string[]>] [-WINSServerType <string[][]>] [-WINSServer <string[][]>] [-iLONICAutoDelay <int[]>] [-iLONICFailOver
<string[]>] [-iLONICFailOverDelay <int[]>] [-iLONICAutoSelect <string[]>] [-iLONICAutoSNPScan <int[]>] [-OutputType <string>] [-Force]
[<CommonParameters>]
ParameterSets : {#{Name=__AllParameterSets; IsDefault=False; Parameters=System.Management.Automation.PSObject[]}}
As in comments, my issue was being cause by the iLO itself/logic rather than the splat being incorrect. My original code does indeed work as intended:
$dnstype = ,#("Primary","Secondary","Tertiary")
$dnsserver = ,#("192.168.1.11","192.168.1.12","192.168.150.13")
$NewDCHPv4Settings = #{
Connection = $connection
InterfaceType = "Dedicated"
OutVariable = "IPv4Set"
OutputType = "Object"
DNSName = $ShortName.ToLower()
ErrorAction = "Stop"
Verbose = $true
}
# Check DNS and amend if required
if ($IPv4Settings.DNSServer -ne $dnsserver) {
Write-Host "DNS server entries not correct, amending"
$NewDCHPv4Settings.Add("DNSServer", $dnsserver)
$NewDCHPv4Settings.Add("DNSServerType", $dnstype)
}
Set-HPEiLOIPv4NetworkSetting #NewDCHPv4Settings
I wrote a 'wrapper' function for the Send-MailMessage function with a hard-coded SmtpServer argument (that's all it does). Here's the header declaring the sets:
function SendMessage {
[CmdletBinding(DefaultParameterSetName='Path')]
Param(
[Parameter(Mandatory,
ParameterSetName='Path',
HelpMessage='the path and filename to the message you would like to send')]
[String]$Path,
[Parameter(Mandatory,
HelpMessage="A string containing the message which you want to send",
ParameterSetName='Msg')]
[String]$Msg,
[Parameter(Mandatory,
HelpMessage='Your Admin Office 365 credentials',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[Alias('Credentials')]
[System.Management.Automation.PSCredential]$Cred,
[Parameter(Mandatory,
HelpMessage='The address to which you want to send the message',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[String[]]$To,
[Parameter(Mandatory,
HelpMessage='The email from which you want to send the message',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[String]$From,
[Parameter(Mandatory,
HelpMessage='The subject of the email to send out',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[ValidateNotNullOrEmpty()]
[String]$Subject,
[Parameter(ParameterSetName='Path',
HelpMessage='[Optional] If you want it cc''d to anyone')]
[Parameter(ParameterSetName='Msg')]
[String[]]$CC
)
# do function stuff
}
The function has two ParameterSets: Path and Msg.
When I run it as a .ps1 script, I can see both sets:
PS U:\> test.ps1
PS U:\> gcm SendMessage -Syntax
SendMessage -Path <string> -Cred <pscredential> -To <string[]> -From <string> -Subject <string> [-CC <string[]>] [<CommonParameters>]
SendMessage -Msg <string> -Cred <pscredential> -To <string[]> -From <string> -Subject <string> [-CC <string[]>] [<CommonParameters>]
But when I import it as a module, I can only see the Path set:
PS U:\> Import-Module -Name test.psm1
PS U:\> gcm SendMessage -Syntax
SendMessage [-Cred] <pscredential> [-Path] <string> [-To] <string> [-From] <string> [-Subject] <string> [[-CC] <string[]>] [<CommonParameters>]
PS U:\>
I would like to be able to import both sets when I import the module.
Is there anything obvious which I am missing?
Thank you.
EDIT: I am using PS version 5.1, and have been testing using the ISE.
Testing scripts and function with the ISE makes it so complex. variables and functions in ISE is always available in global scope. Test this in PowerShell console.
Open a new PowerShell console, and dot source test.ps1
. c:\test.ps1
Get-Command SendMessage -Syntax
Open another PowerShell console and run Import-Module
Import-Module c:\Test.ps.1 -Verbose
Get-Command SendMessage -syntax
I'm writing a function for which two parameters should be exclusive and optional.
Here are valid inputs:
new-event -Title sometitle -Text sometext -TimestampHappened 1234567 -SomeOtherOptionalParam somestring
new-event -Title sometitle -Text sometext -DateHappened (get-date) -SomeOtherOptionalParam somestring
new-event -Title sometitle -Text sometext -SomeOtherOptionalParam somestring
new-event -Title sometitle -Text sometext
Here is an invalid input:
new-event -Title sometitle -Text sometext -DateHappened (get-date) -TimestampHappened 1234567 -SomeOtherOptionalParam somestring
Here is my code so far:
[CmdletBinding()]
# Most parameters belong to Default, New-Event:ByDate and New-Event:ByTimestamp parameter sets
param (
[Parameter(
Position=0,
Mandatory=$True,
ParameterSetName="Default"
)]
[Parameter(
Position=0,
Mandatory=$True,
ParameterSetName="New-Event:ByDate"
)]
[Parameter(
Position=0,
Mandatory=$True,
ParameterSetName="New-Event:ByTimestamp"
)]
[ValidateNotNullOrEmpty()]
[String]$Title,
[Parameter(
Position=1,
Mandatory=$True,
ParameterSetName="Default"
)]
[Parameter(
Position=1,
Mandatory=$True,
ParameterSetName="New-Event:ByDate"
)]
[Parameter(
Position=1,
Mandatory=$True,
ParameterSetName="New-Event:ByTimestamp"
)]
[ValidateNotNullOrEmpty()]
[String]$Text,
[Parameter(
Position=2,
Mandatory=$False,
ParameterSetName="New-Event:ByDate"
)]
[ValidateNotNullOrEmpty()]
[datetime]$DateHappened,
[Parameter(
Position=2,
Mandatory=$False,
ParameterSetName="New-Event:ByTimestamp"
)]
[ValidateNotNullOrEmpty()]
[Double]$TimestampHappened,
[Parameter(
Position=3,
Mandatory=$False,
ParameterSetName="Default"
)]
[Parameter(
Position=3,
Mandatory=$False,
ParameterSetName="New-Event:ByDate"
)]
[Parameter(
Position=3,
Mandatory=$False,
ParameterSetName="New-Event:ByTimestamp"
)]
[String]$SomeOtherParam,
...
Here is what I get when I call Get-Help:
PS> get-help New-Event
NAME
New-Event
SYNOPSIS
Post an event to the stream.
SYNTAX
New-Event [-Title] <String> [-Text] <String> [[-TimestampHappened] <Double>] [[-Priority] <String>] [[-Hostname] <String>] [[-Tags] <String[]>] [[-AlertType] <String>] [<CommonParameters>]
New-Event [-Title] <String> [-Text] <String> [[-DateHappened] <DateTime>] [[-Priority] <String>] [[-Hostname] <String>] [[-Tags] <String[]>] [[-AlertType] <String>] <String>] [<CommonParameters>]
New-Event [-Title] <String> [-Text] <String> [[-Priority] <String>] [[-Hostname] <String>] [[-Tags] <String[]>] [[-AlertType] <String>] [<CommonParameters>]
However here is the error I get when I try to call the function with only the two mandatory parameters:
New-Event -Title test -Text text
New-Event : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ New-Event -Title test -Text text
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Event], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,New-Event
I'm missing something here, but I can't figure out what...
How can I get two parameters that are mutually exclusive and optional?
This makes perfect sense. You have 3 parameter sets, and the 2 mandatory parameters are included on every set. How could PowerShell determine which set you meant to use?
Luckily the [CmdletBinding()] attribute can take a parameter that helps with this exact case: DefaultParameterSetName. Setting this allows PowerShell to use this set in the case of (certain) ambiguities. Use it like so:
[CmdletBinding(DefaultParameterSetName='Default')]
Note that in this case, you named it default; it could have been named anything.
When a script's signature is differentiated (singularly) by an argument, parameter sets make sense to me.
Example:
.\myscript.ps1 -InputFile [-Optional1] [-Optional2]...
.\myscript.ps1 -ArrayOfNames [-Optional1] [-Optional2]...
My question is: Are parameter sets the logical choice when you wish to support parallel (or multiple) dependencies as explained below?
Here is my current scenario.
I'm adding support for an existing script that queries logs containing time stamps. The script should accept a csv file or an array of smtp addresses to identify which users to query.
The script should also support begin and end date parameters or an integer value to facilitate reporting n number of days in the past, calculated from the current date.
The outcome I wish to support is:
.\myScript -InputFile -StartDate -EndDate [-Optional1] [-Optional2]...
.\myScript -InputFile -LastNumDays [-Optional1] [-Optional2]...
.\myScript -Smtp -StartDate -EndDate [-Optional1] [-Optional2]...
.\myScript -Smtp -LastNumDays [-Optional1] [-Optional2]...
Either of the following two parameter definitions work well if I don't attempt to combine my two requirements:
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[ValidateScript({Test-Path -Path $_ -PathType Leaf})][string] $InputFile,
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][String[]] $Smtp
Get-Help displays expected usage as:
.\myScript.ps1 -InputFile <String> [<CommonParameters>]
.\myScript.ps1 -Smtp <String[]> [<CommonParameters>]
If I configure the following instead:
[Parameter(Mandatory=$true, ParameterSetName="NotRange")]
[ValidateNotNullOrEmpty()][int] $LastNumDays = 30, # init 30 days default
[Parameter(Mandatory=$true, ParameterSetName="Range")]
[ValidateNotNullOrEmpty()][Alias("Start")] [DateTime] $StartDate,
[Parameter(Mandatory=$true, ParameterSetName="Range")]
[ValidateNotNullOrEmpty()][Alias("End")] [DateTime] $EndDate
Get-Help displays expected usage as:
.\myScript.ps1 -LastNumDays <Int32> [<CommonParameters>]
.\myScript.ps1 -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]
The problem is that I can't seem to incorporate both my dependencies as described at the beginning of this post. An example of just one of my unsuccessful attempts to combine these two logical dependencies using parameter sets is as follows:
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[ValidateScript({Test-Path -Path $_ -PathType Leaf})][string] $InputFile,
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][String[]] $Smtp,
[Parameter(Mandatory=$true, ParameterSetName="NotRange")]
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][int] $LastNumDays = 30, # init 30 days default
[Parameter(Mandatory=$true, ParameterSetName="Range")]
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][Alias("Start")] [DateTime] $StartDate,
[Parameter(Mandatory=$true, ParameterSetName="Range")]
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][Alias("End")] [DateTime] $EndDate
Get-Help results are incorrect b/c the first two usage statements allow LastNumDays and Start/EndDate parameters to be used at the same time:
.\myScript.ps1 -InputFile <String> -LastNumDays <Int32> -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]
.\myScript.ps1 -Smtp <String[]> -LastNumDays <Int32> -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]
.\myScript.ps1 -LastNumDays <Int32> [<CommonParameters>]
.\myScript.ps1 -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]
I've tested different combinations of mandatory true/false and including/omitting my named parameter sets with no success.
I'm suspecting now that my requirements may not be fitting for the use case parameter sets were intended to support but am left to wonder what pattern and practice I should be using instead.
How can I properly define usage syntax for these two dependencies if not using parameter sets? I feel that I must avoid resorting to tests in my code that announce dependencies that are not defined in Get-Help.
Thank you!
You need to make each parameter set unique, so PowerShell can distinguish one from another. For simplicity reasons I'll name the parameter sets A through D:
A: -InputFile -StartDate -EndDate
B: -InputFile -LastNumDays
C: -Smtp -StartDate -EndDate
D: -Smtp -LastNumDays
Now associate each parameter with each parameter set it appears in:
Param(
[Parameter(Mandatory=$true, ParameterSetName="A")]
[Parameter(Mandatory=$true, ParameterSetName="B")]
[string]$InputFile,
[Parameter(Mandatory=$true, ParameterSetName="C")]
[Parameter(Mandatory=$true, ParameterSetName="D")]
[String[]]$Smtp,
[Parameter(Mandatory=$true, ParameterSetName="B")]
[Parameter(Mandatory=$true, ParameterSetName="D")]
[int]$LastNumDays,
[Parameter(Mandatory=$true, ParameterSetName="A")]
[Parameter(Mandatory=$true, ParameterSetName="C")]
[DateTime]$StartDate,
[Parameter(Mandatory=$true, ParameterSetName="A")]
[Parameter(Mandatory=$true, ParameterSetName="C")]
[DateTime]$EndDate
)
Output:
PS C:\> .\test.ps1 -?
test.ps1 -InputFile <string> -LastNumDays <int> [<CommonParameters>]
test.ps1 -InputFile <string> -StartDate <datetime> -EndDate <datetime> [<CommonParameters>]
test.ps1 -Smtp <string[]> -LastNumDays <int> [<CommonParameters>]
test.ps1 -Smtp <string[]> -StartDate <datetime> -EndDate <datetime> [<CommonParameters>]
Note that it's pointless to provide a default value for a mandatory parameter (-LastNumDays), because you're required to provide a value anyway.
I want to create a replacement function for Write-Error. To do this, it must support the same parameter sets as the original CmdLet.
I can see what parameter sets Write-Error supports by using
Get-Help Write-Error
or online on technet
NAME
Write-Error
SYNOPSIS
Writes an object to the error stream.
SYNTAX
Write-Error [-Message] [-Category ] [-CategoryActivity ] [-CategoryReason ]
[-CategoryTargetName ] [-CategoryTargetType ] [-ErrorId ] [-RecommendedAction ] [-
TargetObject ] []
Write-Error [-Category <ErrorCategory>] [-CategoryActivity <String>] [-CategoryReason <String>] [-CategoryTargetNam
e <String>] [-CategoryTargetType <String>] [-ErrorId <String>] [-Message <String>] [-RecommendedAction <String>] [-
TargetObject <Object>] -Exception <Exception> [<CommonParameters>]
Write-Error [-CategoryActivity <String>] [-CategoryReason <String>] [-CategoryTargetName <String>] [-CategoryTarget
Type <String>] [-RecommendedAction <String>] -ErrorRecord <ErrorRecord> [<CommonParameters>]
But how can i easily create a new PowerShell script CmdLet that uses the exact same parameter sets (including required parameters and default values)?
This is a perfect use case for proxy function. You can create proxy function using command metadata:
$command = Get-Command Write-Error
$proxy = [System.Management.Automation.ProxyCommand]::Create($command)
Some links that should help explain it:
Scripting Guys blog (post by PS MVP Shay Levy)
PowerShell Team blog
There is also PowerShell module (pspx) that could get you started.