Add square bracket only if one set of values [duplicate] - powershell

This question already has answers here:
ConvertTo-JSON an array with a single item
(7 answers)
Closed 4 years ago.
I'm using this scrip for Azure monitoring with Zabbix:https://b-blog.info/en/monitoring-azure-resources-with-zabbix.html
If only one SQL database in output:
{"data":{
"{#SERVERNAME}": "mojsql",
"{#ID}": "/subscriptions/11111-222222/resourceGroups/rg/providers/Microsoft.Sql/servers/mojsql/databases/mojabaza",
"{#DATABASENAME}": "mojabaza",
"{#RGNAME}": "rg"
}
}
If more than one database then output is:
{"data":[
{
"{#DATABASENAME}": "mojabaza",
"{#SERVERNAME}": "mojsql",
"{#RGNAME}": "rg",
"{#ID}": "/subscriptions1111-22222/resourceGroups/rg/providers/Microsoft.Sql/servers/mojsql/databases/mojabaza"
},
{
"{#DATABASENAME}": "mojabaza1",
"{#SERVERNAME}": "mojsql",
"{#RGNAME}": "rg",
"{#ID}": "/subscriptions/11111-22222/resourceGroups/rg/providers/Microsoft.Sql/servers/mojsql/databases/mojabaza1"
}
]
}
Note square brackets are added automatically.
The issue is if only one database in resource group then i must add square brackets in order to see database in zabbix
'{"data":' + '[' + $($result | ConvertTo-Json) + ']' + "`n}";
If more than one database then double square brackets are added and Zabbix shows error.
Is it possible to write a condition if one output then add square brackets something like
if $($results)=1 then
'{"data":' + '[' + $($result | ConvertTo-Json) + ']' + "`n}";
else
'{"data":' + $($result | ConvertTo-Json) + "`n}";

Your use case is exactly what the PowerShell's array subexpression operator, #(), was designed for:
'{"data":' + (ConvertTo-Json #($result)) + "`n}"
Wrapping a command in #(...) guarantees that its output is an array.
In other words:
If the output already is an array (collection), it is, loosely speaking, left alone (technically, a copy of the output array is typically created).
If it isn't, it is wrapped in a array, as the that array's only element.
Additionally, in order for ConvertTo-Json to see the array as such, you mustn't use the pipeline and instead pass it as an argument:
Because the pipeline unwraps (unrolls) arguments implicitly, the following two commands are identical:
$result | ...
#($result) | .... # !! identical

Related

How to map an array of strings into a new array of strings? [duplicate]

This question already has answers here:
Select/map each item of a Powershell array to a new array
(3 answers)
Closed 1 year ago.
I'm trying to write some PowerShell code that takes an array of suffixes and builds a new array with an inputted string prefixed to those suffixes.
How can I do something like C#'s Map or aggregation code to build a new list based off the original list?
In the below powershell ...
$workspaceName = "test"
$deleteableItemSuffixes = #("-api","-asp","-appi")
$deleteableItemNames = ???
I want the resulting array to be ["test-api", "test-asp", "test-appi"], for example.
The -replace operator allows for a concise and efficient solution for string arrays:
# Returns array #('test-api', 'test-asp', 'test-appi')
"-api", "-asp", "-appi" -replace '^', 'test'
-replace is regex-based and accepts arrays as input, in which case the operation is performed on each element.
^ matches the position at the beginning of the string, at which the substitution string, 'test' is inserted, in effect prepending that string to each array element (by contrast, $ matches the end of each string and can be used to append a value).
For other data types and if you need more flexibility, you can use the .ForEach() array method (which, in the case of a collection that is already in memory, is faster than the ForEach-Object cmdlet):
("-api", "-asp", "-appi").ForEach({ 'test' + $_ })
Strictly speaking, what .ForEach() returns isn't a .NET array, but a collection of type [System.Collections.ObjectModel.Collection[psobject]], but the difference usually doesn't matter in PowerShell.
If you do need an actual array, you can simply cast the result:
# Use the appropriate data type, or simply [array] for an [object[]] array.
[string[]] ("-api", "-asp", "-appi").ForEach({ 'test' + $_ })

Create an incrementing variable from 2 variables in PowerShell

OK, First I consider myself a newbie and have much to learn about PowerShell and this is my first post ever.
I am trying to loop through some data and put it into a custom object and put them into separate arrays for later use. The issue is that I want to create a variable representing $week_data1 by using a counter $i so I can reduce the amount of code required. I do have a concatenated variable being written out: write-host '$week++ ='$week$i But I think it is being represented as a string?
How can I get $week_data$i to represent the array to insert the data?
Input data. Each week ends on Saturday.
$week1=#('2021-05-01')
$week2=#('2021-05-02', '2021-05-03', '2021-05-04', '2021-05-05', '2021-05-06', '2021-05-07', '2021-05-08')
$week3=#('2021-05-09', '2021-05-10', '2021-05-11', '2021-05-12', '2021-05-13', '2021-05-14', '2021-05-15')
$week4=#('2021-05-16', '2021-05-17', '2021-05-18', '2021-05-19', '2021-05-20', '2021-05-21', '2021-05-22')
$week5=#('2021-05-23', '2021-05-24', '2021-05-25', '2021-05-26', '2021-05-27', '2021-05-28', '2021-05-29')
$week6=#('2021-05-30', '2021-05-31')
$month =#($week1, $week2, $week3, $week4, $week5, $week6)
Create the output structures to be populated.
$week_data1=#()
$week_data2=#()
$week_data3=#()
$week_data4=#()
$week_data5=#()
$week_data6=#()
$month_data =#($week_data1, $week_data2, $week_data3, $week_data4, $week_data5, $week_data6)
Loop through the array and count the week number that is being processed.
$i = 0
foreach($week in $month)
{ $i++
$n=0
Here I can write out a Variable and it concatenates properly.
**write-host '$week++ ='$week$i**
foreach($day in $week)
{$n++
write-host '$day ='$day
Pull in data from a .csv file to populate the custom object.
foreach($line in $csv)
{
if($line -match $day)
Match the line in the CSV file that has the correct Date in it. One line in the file per date in the month.
{ #write-host '$line.Day = ' $line.Day
# custom object to be used later
$date_data = [PSCustomObject] #{
week_numb = $i
date = $line.Day
attempts = $line.Attempts
connects = $line.Connects
}
I have tried different syntax versions but it does not work here? I want to put the custom object data into the new array for the week being processed.
#write-host '$week_data[$i]='$week_data[$i]
$week_data$i += $date_data # Add data from csv file into a
#$week_data[$i] += $date_data
}
}
}
}
Issue using $week_data$i as a variable I get an error:
At line:38 char:17
$week_data$i += $date_data # Add data from csv file into a
~~
Unexpected token '$i' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
You're looking for variable indirection, i.e. the ability to refer to a variable indirectly, by a name stored in another variable or returned from an expression.
Note, however, that there are usually superior alternatives, such as using arrays or hashtables as multi-value containers - see this answer for an example.
If you do need to use variable indirection, use Get-Variable and Set-Variable:
$week_data1 = 'foo', 'bar'
$i = 1
# Same as: $week_data1
# Note that "$" must NOT be specified as part of the name.
Get-Variable "week_data$i" -ValueOnly
# Same as: $week_data1 = 'baz', 'quux'
Set-Variable "week_data$i" baz, quux
# Updating an existing value requires nesting the two calls:
# Same as: $week_data1 += 'quuz'
Set-Variable "week_data$i" ((Get-Variable "week_data$i" -ValueOnly) + 'quuz')
As an aside: "extending" an array with += is convenient, but inefficient: a new array must be created behind the scenes every time - see this answer.
Similarly, calling cmdlets to set and get variables performs poorly compared to direct assignments and variable references.
See this answer for applying the indirection technique analogously to environment variables, using Get-Content / Set-Content and the Env: drive.
As for what you tried:
$week_data$i = ... is an assignment expression, which is interpreted as directly juxtaposing two variables, $week_data and $i, which causes the syntax error you saw.
By contrast, something like Write-Output $week_data$i is a command, and while $week_data$i is also interpreted as two variable references, as a command argument it is syntactically valid, and would simply pass the (stringified) concatenation of the two variable values; in other words: $week_data$i acts as if it were double-quoted, i.e. an expandable string, and the command is therefore equivalent to Write-Output "$week_data$i"
Unrelated to the answer, but likely helpful for you, I have a function that will determine what week in a month a given date is.
Function Get-Week{
[cmdletbinding()]
param([parameter(ValueFromPipeline)][string[]]$Date)
process{
ForEach($Day in $Date){
$DTDay=[datetime]$Day
$Buffer = ([datetime]("{0}-01-{1}" -f $DTDay.Month,$DTDay.Year)).dayofweek.value__ -1
[math]::Truncate(($DTDay.Day+$Buffer)/7)+1
}
}
}
So you feed that a string that can be converted to a date like:
'5-13-2021' | Get-Week
or
Get-Week '5-13-2021'
and you get back a number indicating what week (ending on Saturday) of the month that day falls in.

Powershell color formatting with format operator

I'm using a format operator inside a script with a following example:
$current = 1
$total = 1250
$userCN = "contoso.com/CONTOSO/Users/Adam Smith"
"{0, -35}: {1}" -f "SUCCESS: Updated user $current/$total", $userCN
This excpectedly shows the following output:
SUCCESS: Updated user 1/1250 : contoso.com/CONTOSO/Users/Adam Smith
The format operator is there to keep the targeted output text in place with current / total running numbers varying between 1-99999. Without the format operator I could highlight the success line like this:
Write-Host -BackgroundColor Black -ForegroundColor Green "SUCCESS: Updated user $current/$total: $userCN"
But the question is how could I use the highlight-colors combined with the format operator? There's only the -f parameter and it doesn't allow the color parameters because, well, it's not the same thing as Write-Host in the first place.
Unlike other shells, PowerShell allows you to pass commands and expressions as command arguments simply by enclosing them in parentheses, i.e by using (...), the grouping operator.
When calling PowerShell commands (cmdlets, scripts, functions), the output is passed as-is as an argument, as its original output type.
Therefore, Theo's solution from a comment is correct:
Write-Host -BackgroundColor Black -ForegroundColor Green `
("{0, -35}: {1}" -f "SUCCESS: Updated user $current/$total", $userCN)
That is, the -f expression inside (...) is executed and its output - a single string in this case - is passed as a positional argument to Write-Host (implicitly binds to the -Object parameter).
Note that you do not need, $(...), the subexpression operator, in this case:
(...) is sufficient to enclose an expression or command.
In fact, in certain cases $(...) can inadvertently modify your argument, because it acts like the pipeline; that is, it enumerates and rebuilds array expressions as regular PowerShell arrays (potentially losing strong typing) and unwraps single-element arrays:
# Pass a single-element array to a script block (which acts like a function).
# (...) preserves the array as-is.
PS> & { param($array) $array.GetType().Name } -array ([array] 1)
Object[] # OK - single-element array was passed as-is
# $(...) unwraps it.
PS> & { param($array) $array.GetType().Name } -array $([array] 1)
Int32 # !! Single-element array was unwrapped.
# Strongly-typed array example:
PS> & { param($array) $array.GetType().Name } ([int[]] (1..10))
Int32[] # OK - strongly typed array was passed as-is.
# Strongly-typed array example:
PS> & { param($array) $array.GetType().Name } $([int[]] (1..10))
Object[] # !! Array was *enumerated and *rebuilt* as regular PowerShell array.
The primary use of $(...) is:
expanding the output from expressions or commands inside expandable strings (string interpolation)
To send the output from compound statements such as foreach (...) { ... } and if (...) { ... } or multiple statements directly through the pipeline, after collecting the output up front (which (...) does as well); however, you can alternatively wrap such statements & { ... } (or . { ... } in order to execute directly in the caller's scope rather than a child scope) in order to get the usual streaming behavior (one-by-one passing of output) in the pipeline.
Taking a step back: Given that you already can use compound statements as expressions in variable assignments - e.g., $evenNums = foreach ($num in 1..3) { $num * 2 } - and expressions generally are accepted as the first segment of a pipeline - e.g., 'hi' | Write-Host -Fore Yellow - it is surprising that that currently doesn't work with compound statements; this GitHub issue asks if this limitation can be lifted.
In the context of passing arguments to commands:
Use $(...), the subexpression operator only if you want to pass the output from multiple commands or (one or more) compound statements as an argument and/or, if the output happens to be a single object, you want that object to be used as-is or, if it happens to be a single-element array (enumerable), you want it to be unwrapped (pass the element itself, not the array.
Of course, if you're constructing a string argument, $(...) can be useful inside that string, for string interpolation - e.g., Write-Host "1 + 1 = $(1 + 1)"
Use #(...), the array subexpression operator only if you want to pass the output from multiple commands as an argument and/or you want to ensure that the output becomes a array; that is, the output is returned as a (regular PowerShell) array, of type [object[]], even if it happens to comprise just one object. In a manner of speaking it is the inverse of $(...)'s behavior in the single-object output case: it ensures that single-object output too becomes an array.

Behaviour of PowerShell when Combining HashTables

Question
Why is $null + #{} valid, but #{} + $null not; even where null is cast to a hashtable (#{} + ([hashtable]$null)).
Example Code
[hashtable]$a = #{demo1=1;demo2='two'}
[hashtable]$b = #{demo3=3;demo4='Ivy'}
[hashtable]$c = $null
#combining 2 hashtables creates 1 with both hashes properties (would error if any properties were common to both)
write-verbose 'a + b' -Verbose
($a + $b)
#combining a null hashtable with a non-null hashtable works
write-verbose 'c + a' -Verbose
($c + $a)
#combing 2 null hashtables is fine; even if we've not explicitly cast null as a hashtable
write-verbose 'c + null' -Verbose
($c + $null)
#however, combinging a hashtable with null (i.e. same as second test, only putting null as the right argument instead of the left, produces an error
write-verbose 'a + c' -Verbose
($a + $c)
Output
Name Value
---- -----
demo3 3
demo4 Ivy
demo1 1
demo2 two
VERBOSE: c + a
demo1 1
demo2 two
VERBOSE: c + d
VERBOSE: a + c
A hash table can only be added to another hash table.
At line:19 char:1
+ ($a + $c)
+ ~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : AddHashTableToNonHashTable
Side Note
Incidentally, this led me to discover this useful trick for a null-coalescing operation for hashtables: ($c + #{}). e.g. ($a + ($c + #{})) avoids the error produced above / (($a + #{}) + ($c + #{})) gives us a completely safe way to add hashtables where either value may be null.
I tried to find out exactly why, but I can't for sure.
My original beliefs were:
There may be some PowerShell implicit type coercion happening, where the thing on the right is cast to match the type on the left. e.g. with "1"+1 the 1 on the right becomes a string and the output is "11", but 1+"1" the right becomes a number and the output is 2.
This definitely is not happening, or $null + #{} would either throw a cast error, or cast and do null + null = null, not output an empty hashtable as it does.
Addition of A + B for things other than numbers has some basic rules for things like array concatenation, but beyond that, it will fall down to the .Net Framework underneath and will try to do a method call like A.op_Addition(B) and it will be up to the thing on the left to say what happens when you try to add the thing on the right to it.
This does happen, but is not happening here. Consider #{} + 0 tells you that a hashtable can only be added to a hashtable. (Lies, you can add it to null). And 0 + #{} tells you that hashtables do not contain an op_Addition method. So the addition with null seems like it should give that error, but doesn't, because it's not falling back to this mechanism.
The PSv3 language spec (which I think is the latest version available), download here: https://www.microsoft.com/en-us/download/details.aspx?id=36389, mentions addition:
7.7.1 Addition Description: The result of the addition operator + is the sum of the values designated by the two operands after the usual
arithmetic conversions (§6.15) have been applied.
The usual arithmetic conversions say:
If neither operand designates a value having numeric type, then [..] all operands designating the value $null are converted to zero of type int and the process continues with the numeric conversions listed below.
That implies $null gets converted to 0 in both your examples. Although that cannot be happening because 0 + #{} and #{} + 0 are both errors.
There is an explicit mention of adding of two hashtables in section 7.7:
7.7.4 Hashtable concatenation Description: When both operands designate Hashtables the binary + operator creates a new Hashtable
that contains the elements designated by the left operand followed
immediately by the elements designated by the right operand.
OK, hashtable addition is handled by the PowerShell engine, but only adding two hashtables.
Section 7. introduction says:
Windows PowerShell: If an operation is not defined by PowerShell, the type of the value designated by the left operand is inspected to see if it has a corresponding op_ method.
And the operation $null + Hashtable doesn't seem to be defined by PowerShell from what I can see in the spec, and the corresponding method for + is op_Addition - a method which hashtables do not have, see error code earlier - and this is not throwing that error, and not only that but in the case of adding to 0 the error comes from the Right operand not the left one.
And the other interesting bit in the spec is:
4.1.2 The characteristics of the null type are unspecified.
So the summary appears to be:
#{} + $null - is triggering the PowerShell handling of adding two hashtables, even though the spec doesn't say that it will.
$null + #{} - it looks like there's an implicit $null + x = x rule, although the spec doesn't seem to mention it, and it might be implementation dependent.
[<type>]$null - casting $null to a numeric type results in 0 (6.15 arithmetic conversions), but casting it to anything else(?) appears to result in $null (not in the spec).
The comment and linked chain which says $null has no type are against the PowerShell spec 4.1.2 "the null type" which says "The null type has one instance, the automatic variable $null (§2.3.2.2), also known as the null value. The characteristics of this type are unspecified." so at least in terminology, it's described as a type in PowerShell even if you can't GetType() on it ..

Extracting specific data from a string with regex using Powershell

I'm returning some data like this in powershell :
1)Open;#1
2)Open;#1;#Close;#2;#pending;#6
3)Closed;#5
But I want an output like this :
1)1 Open
2)
1 Open
2 Close
6 pending
3)
5 Closed
The code:
$lookupitem = $lookupList.Items
$CMRSItems = $list.Items | where {$_['ID'] -le 5}
$CMRSItems | ForEach-Object {
$realval = $_['EventType']
Write-Host "RefNumber: " $_['RefID']
Write-Host $realval
}
Any help would be appreciated as my powershell isn't that good.
Without regular expressions, you could do something like the following:
Ignore everything up to the first ')' character
Split the string on the ';' character
foreach pair of the split string
the state is the first part (ignore potentially leading '#')
the number is the second part (ignore leading '#')
Or you could do it using the .NET System.Text.RegularExpressions.Regex class with the following regular expression:
(?:#?(?<state>[a-zA-Z]+);#(?<number>\d);?)
The Captures property on the MatchCollection returned by the Matches method would be a collection in which each item will contain two instances in the Group collection; named state and number respectively.