Splat parameters to a cmdlet without unrolling array - powershell

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

Related

Levels of parameter sets for a cmdlet

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'.

How to handle error response from AZ REST

How can I catch and handle error responses from Azure CLI az rest command?
If I paste the following into my console (Powershell 7.1 running on Windows), I see some error text written to the console, but it doesn't actually throw which would enter the catch block, nor is anything written into $rv.
What am I missing here and how can I inspect the return value of az rest after an error?
try {
$rv = (az rest --method post --url "https://graph.microsoft.com/v1.0/servicePrincipals/$DELIBERATE_ERROR") #--body $body)
Write-Host "rv:"
$rv
}
catch {
Write-Host "error:"
$error
}
Note: I also saved the above in a file called Untitled-2.ps1 and ran it but you can just copy/paste it into console.
PS D:\code\Powershell> .\Untitled-2.ps1
ERROR: Bad Request({"error":{"code":"BadRequest","message":"Write requests (excluding DELETE) must contain the Content-Type header declaration.","innerError":{"date":"2021-09-13T06:02:39","request-id":"447b4cdc-cc43-4e38-a960-d389a3ea3a87","client-request-id":"447b4cdc-cc43-4e38-a960-d389a3ea3a87"}}})
rv:
PS D:\code\Powershell>
Thank you John Hanley. Posting your suggestions as answer to help other community members.
As you $rv is the result written to stdout and you want to capture stderr.
The data from stdout will be strings while stderr produces
System.Management.Automation.ErrorRecord objects.
```swift
$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
```
and,
If you want to capture standard out and error with start process without sending to file.
Start-Process
[-FilePath] <String>
[[-ArgumentList] <String[]>]
[-Credential <PSCredential>]
[-WorkingDirectory <String>]
[-LoadUserProfile]
[-NoNewWindow]
[-PassThru]
[-RedirectStandardError <String>]
[-RedirectStandardInput <String>]
[-RedirectStandardOutput <String>]
[-WindowStyle <ProcessWindowStyle>]
[-Wait]
[-UseNewEnvironment]
[-WhatIf]
[-Confirm]
[<CommonParameters>]
Check Start Process document for better understanding.
Also check the SO1 and SO2 with related information.

Azure Function and PowerShell: unable to set connection strings with Set-AzWebApp

For deployment purposes I created a PowerShell script to set App Settings. This works fine via
$currentAppSettings = $app.SiteConfig.AppSettings
$appSettings = #{}
# Add existing App Settings
ForEach ($currentAppSetting in $currentAppSettings) {
$appSettings[$currentAppSetting.Name] = $currentAppSetting.Value
}
# Add new App Settings
$appSettings["someKey"] = "someValue"
# Update App Settings
Set-AzWebApp -ResourceGroupName $resourceGroupName -Name $appName -AppSettings $appSettings
As I am using now Entity Framework I also need to set the Connection Strings. First I thought this should work like with App Settings and just adding the ConnectionStrings Argument:
$currentConnectionStrings = $app.SiteConfig.ConnectionStrings
$connectionStrings = #{}
ForEach ($currentConnectionString in $currentConnectionStrings) {
$connectionStrings[$currentConnectionString.Name] = $currentConnectionString.ConnectionString
}
$connectionStrings["someKey"] = "someValue"
Set-AzWebApp -ResourceGroupName $resourceGroupName -Name $appName -ConnectionStrings $connectionStrings
But this fails with the error
Set-AzWebApp : Cannot validate argument on parameter 'ConnectionStrings'. Connection string type value pair must be of type 'System.Collections.Hashtable'
despite the fact that $connectionStrings is of type System.Collections.Hashtable
Also trying to work with Custom Objects, arrays and so on failed.
How do I pass the connection strings correctly?
And how do I specify the Type (e.g. SQLAZURE)?
Thanks
As per the Documentation of Set-AzwebApp ConnectionStrings should be Hastable.
Set-AzWebApp
[[-AppServicePlan] <String>]
[[-DefaultDocuments] <String[]>]
[[-NetFrameworkVersion] <String>]
[[-PhpVersion] <String>]
[[-RequestTracingEnabled] <Boolean>]
[[-HttpLoggingEnabled] <Boolean>]
[[-DetailedErrorLoggingEnabled] <Boolean>]
[[-AppSettings] <Hashtable>]
[[-ConnectionStrings] <Hashtable>]
[[-HandlerMappings] <System.Collections.Generic.IList`1[Microsoft.Azure.Management.WebSites.Models.HandlerMapping]>]
[[-ManagedPipelineMode] <String>]
[[-WebSocketsEnabled] <Boolean>]
[[-Use32BitWorkerProcess] <Boolean>]
[[-AutoSwapSlotName] <String>]
[-ContainerImageName <String>]
[-ContainerRegistryUrl <String>]
[-ContainerRegistryUser <String>]
[-ContainerRegistryPassword <SecureString>]
[-EnableContainerContinuousDeployment <Boolean>]
[-HostNames <String[]>]
[-NumberOfWorkers <Int32>]
[-AsJob]
[-AssignIdentity <Boolean>]
[-HttpsOnly <Boolean>]
[-AzureStoragePath <WebAppAzureStoragePath[]>]
[-ResourceGroupName] <String>
[-Name] <String>
[-DefaultProfile <IAzureContextContainer>]
[<CommonParameters>]
You should set connectionstring using a Hashtable as shown below:
$connectionStrings = #{connectionStrings = #{Name="<ConnectionStringName>";Value="<ConnectionSyring>";type="<SQLAzure/SQLServer/Custom/MySql>"}}
Set-AzWebApp -ResourceGroupName $resourceGroupName -Name $appName -ConnectionStrings $connectionStrings
#KetanChawda-MSFT: Thanks for your advice, it helped me a lot to develop the solution (but did not work as such in my context as mentioned below).
Here is the way it works for me now:
$currentConnectionStrings = $app.SiteConfig.ConnectionStrings
$connectionStrings = #{}
# Add existing connection strings
ForEach ($currentConnectionString in $currentConnectionStrings) {
$connectionStrings[$currentConnectionString.Name] =
#{
Value=$currentConnectionString.ConnectionString;
Type=($currentConnectionString.Type | Out-String).ToUpper()
}
}
# Add database connection string
$connectionStrings["connName"] = #{
Value="connString";
Type="SQLAZURE"
}
# Update settings
Set-AzWebApp -ResourceGroupName $resourceGroupName -Name $appName -ConnectionStrings $connectionStrings
What is interesting is that in case of existing connection strings I have to format it as string and use toUpper, otherwise it does not work for me (Error: Set-AzWebApp : Cannot validate argument on parameter 'ConnectionStrings'. Connection string type must be specified.). When adding a new connection string I can use e.g. SQLAZURE as well as SQLAzure.
EDIT:
Here is an approach where you don't need the connection strings section at all when working with Entity Framework and Azure Functions: Missing ProviderName when debugging AzureFunction as well as deploying azure function

Using parametersets to constrain more than one mutually exclusive dependency

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.

Copy parameter sets from a CmdLet to new CmdLet

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.