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